selector = "form.neuron-form"
formHasChanged = false

$(document).on "page:load", ->
  formHasChanged = false

$(document).on "change", "#{selector} input", ->
  formHasChanged = true

$(document).on "page:before-change", ->
  confirm(
    "La información no ha sido guardada. ¿Estás seguro de continuar?"
  ) if formHasChanged

selector = "form.neuron-form"
formHasChanged = false

$(document).on "page:load", ->
  formHasChanged = false

$(document).on "change", "#{selector} input, #{selector} textarea", ->
  formHasChanged = true

$(document).on "page:before-change", ->
  confirm(
    "La información no ha sido guardada. ¿Estás seguro de continuar?"
  ) if formHasChanged

Add textarea to listen changes in neuron form


App.FoundationExtras =

  initialize: ->
    $(document).foundation()
    $(window).trigger "load.zf.sticky"
    $(window).trigger "resize"

    clearSticky = ->
      $("[data-sticky]").foundation("destroy") if $("[data-sticky]").length

    $(document).on("page:before-unload", clearSticky)

    window.addEventListener("popstate", clearSticky, false)

App.FoundationExtras =

  initialize: ->
    $(document).foundation()
    $(window).trigger "init.zf.sticky"
    $(window).trigger "resize"

    clearSticky = ->
      $("[data-sticky]").foundation("destroy") if $("[data-sticky]").length

    $(document).on("page:before-unload", clearSticky)

    window.addEventListener("popstate", clearSticky, false)

Replace load to init sticky

Replace load to init sticky


Menglifang.Widgets.DatetimePicker = Ember.TextField.extend
  resetable: true

  format: 'yyyy-mm-dd hh:ii'
  autoclose: true
  todayBtn: true
  startDate: '1949-10-01'
  minuteStep: 10
  minView: 0
  maxView: 4
  language: 'zh-CN'

  didInsertElement: ->
    options =
      format: @get('format')
      autoclose: @get('autoclose')
      todayBtn: @get('todayBtn')
      startDate: @get('startDate')
      minuteStep: @get('minuteStep')
      minView: @get('minView')
      maxView: @get('maxView')
      language: @get('language')

    @$().datetimepicker(options)

Ember.Handlebars.helper 'datetime-picker', Menglifang.Widgets.DatetimePicker

Menglifang.Widgets.DatetimePicker = Ember.TextField.extend
  resetable: true

  format: 'yyyy-mm-dd hh:ii'
  autoclose: true
  todayBtn: false
  startDate: '1949-10-01'
  minuteStep: 10
  minView: 0
  maxView: 4
  language: 'zh-CN'

  didInsertElement: ->
    options =
      format: @get('format')
      autoclose: @get('autoclose')
      todayBtn: @get('todayBtn')
      startDate: @get('startDate')
      minuteStep: @get('minuteStep')
      minView: @get('minView')
      maxView: @get('maxView')
      language: @get('language')

    @$().datetimepicker(options)

Ember.Handlebars.helper 'datetime-picker', Menglifang.Widgets.DatetimePicker

Set default value of todayBtn to false.

Set default value of todayBtn to false.


`import Em from "vendor/ember"`

Component = Em.Component.extend
    click: (evt) ->
        if evt.target.tagName is "A" and evt.target.target != '_blank'
            evt.stopPropagation()
            evt.preventDefault()
            @container.lookup("route:openxpki").sendAjax
                data:
                    page:evt.target.href.split("#")[1]
                    target:evt.target.target
        else if evt.target.tagName is "BUTTON"
            $(evt.target).addClass "btn-loading"

    buttonsWithDescription: (->
        @get("content.content.buttons").isAny "description"
    ).property "content.content.buttons.@each.description"

    actions:
        execute: (btn) ->
            if btn.action
                @container.lookup("route:openxpki").sendAjax
                    data:
                        action:btn.action
            else
                console.log('Transition');
                @container.lookup("route:openxpki").transitionTo "openxpki", btn.page

`export default Component`

`import Em from "vendor/ember"`

Component = Em.Component.extend
    click: (evt) ->
        if evt.target.tagName is "A" and evt.target.target != '_blank'
            evt.stopPropagation()
            evt.preventDefault()
            @container.lookup("route:openxpki").sendAjax
                data:
                    page:evt.target.href.split("#")[1]
                    target:evt.target.target
        else if evt.target.tagName is "BUTTON"
            $(evt.target).addClass "btn-loading"

    buttonsWithDescription: (->
        buttons = @get "content.content.buttons"
        if buttons
            buttons.isAny "description"
        else
            false
    ).property "content.content.buttons.@each.description"

    actions:
        execute: (btn) ->
            if btn.action
                @container.lookup("route:openxpki").sendAjax
                    data:
                        action:btn.action
            else
                console.log('Transition');
                @container.lookup("route:openxpki").transitionTo "openxpki", btn.page

`export default Component`

Fix property calculation in absence of buttons.


Helpers = require '../lib/helpers'

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

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

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

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

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

Add test for proper validated linter.

Add test for proper validated linter.


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

{CloneAssignmentLink} = require './task-dnd'

isEmpty = require 'lodash/isEmpty'
partial = require 'lodash/partial'

{PastTaskPlansActions, PastTaskPlansStore} = require '../../flux/past-task-plans'
LoadableItem = require '../loadable-item'

EmptyWarning = (props) ->
  return null unless props.isVisible

    No assignments were found

PastAssignmentsLoading = ->

      Loading past assignments...

PastAssignments = React.createClass

  propTypes:
    courseId: React.PropTypes.string.isRequired

  render: ->
    plans = PastTaskPlansStore.get(@props.courseId) or []

      <EmptyWarning isVisible={isEmpty(plans)} />

      {for plan in plans
        <CloneAssignmentLink key={plan.id} plan={plan} />}

PastAssignmentsShell = React.createClass
  render: ->
    {courseId} = @props

    <LoadableItem
      id={courseId}
      store={PastTaskPlansStore}
      actions={PastTaskPlansActions}
      load={partial(PastTaskPlansActions.load, {courseId})}
      renderLoading={ PastAssignmentsLoading }
      renderItem={-> <PastAssignments courseId={courseId} />}
    />

module.exports = PastAssignmentsShell

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

{CloneAssignmentLink} = require './task-dnd'

isEmpty = require 'lodash/isEmpty'
partial = require 'lodash/partial'

{PastTaskPlansActions, PastTaskPlansStore} = require '../../flux/past-task-plans'
LoadableItem = require '../loadable-item'

PastAssignmentsLoading = ->

      Loading past assignments...

PastAssignments = React.createClass

  propTypes:
    courseId: React.PropTypes.string.isRequired

  render: ->
    plans = PastTaskPlansStore.get(@props.courseId) or []

      {for plan in plans
        <CloneAssignmentLink key={plan.id} plan={plan} />}

PastAssignmentsShell = React.createClass
  render: ->
    {courseId} = @props

    <LoadableItem
      id={courseId}
      store={PastTaskPlansStore}
      actions={PastTaskPlansActions}
      load={partial(PastTaskPlansActions.load, {courseId})}
      renderLoading={ PastAssignmentsLoading }
      renderItem={-> <PastAssignments courseId={courseId} />}
    />

module.exports = PastAssignmentsShell

Hide empty warning on past assignments

Hide empty warning on past assignments


noflo = require "../../lib/NoFlo"

class Split extends noflo.Component
  description: "This component receives data on a single input port and sends the same data out to all connected output ports"

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

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

exports.getComponent = ->
  new Split

noflo = require "../../lib/NoFlo"

class Split extends noflo.Component
  description: "This component receives data on a single input port and sends the same data out to all connected output ports"

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

    @inPorts.in.on "connect", =>
      @outPorts.out.connect()
    @inPorts.in.on "begingroup", (group) =>
      @outPorts.out.beginGroup group
    @inPorts.in.on "data", (data) =>
      @outPorts.out.send data
    @inPorts.in.on "endgroup", =>
      @outPorts.out.endGroup()
    @inPorts.in.on "disconnect", =>
      @outPorts.out.disconnect()

exports.getComponent = ->
  new Split

Connect when getting upstream connection


# Description
#   @lefay do you ...?
#
# Author:
#   lcaro

module.exports = (robot) ->
  robot.respond /do you (.+)$/i, (msg) ->
    msg.reply "yes I do in fact " + msg.match[1]

# Description
#   @lefay do you ...?
#
# Author:
#   lcaro

module.exports = (robot) ->
  robot.respond /do you (.+)\??$/i, (msg) ->
    msg.reply "yes I do in fact " + msg.match[1]

Allow question marks in do you

Allow question marks in do you


Config = require '../config'
argv = require('optimist')
  .usage('Usage: $0 --projectKey key --clientId id --clientSecret secret')
  .demand(['projectKey','clientId', 'clientSecret'])
  .argv
MarketPlaceStockUpdater = require('../main').MarketPlaceStockUpdater

Config.timeout = 60000
Config.showProgress = true

updater = new MarketPlaceStockUpdater(Config, argv.projectKey, argv.clientId, argv.clientSecret)
updater.run (msg) ->
  console.log msg
  process.exit 1 unless msg.status

Config = require '../config'
argv = require('optimist')
  .usage('Usage: $0 --projectKey key --clientId id --clientSecret secret')
  .demand(['projectKey','clientId', 'clientSecret'])
  .argv
MarketPlaceStockUpdater = require('../main').MarketPlaceStockUpdater

updater = new MarketPlaceStockUpdater(Config, argv.projectKey, argv.clientId, argv.clientSecret)
updater.run (msg) ->
  console.log msg
  process.exit 1 unless msg.status

Remove config as it shoud be set outside.


class Utils

    @getHTMLTitleFromHistoryFragment: (fragment) ->
        _.capitalize(fragment.split('\/').join(' '))

    @getShortTaskID: (taskId) ->
        split = taskId.split(/\-|\:|\./)
        if split.length > 1
            return "#{ split[0] }..."
        else
            if taskId.length > 20
                return "#{ taskId.substr(0, 20) }..."
        return taskId

    @stringJSON: (object) ->
        JSON.stringify object, null, '    '

    @viewJSON: (type, objectId) ->
        lookupObject = {}

        if type is 'task'
            lookupObject = app.allTasks
        if type is 'request'
            lookupObject = app.allRequests

        vex.dialog.alert
            contentCSS:
                width: 800
            message: "<pre>#{ lookupObject[objectId].JSONString }</pre>"

    @setupSortableTables: ->
        sortable.init()

    @humanTimeAgo: (date) ->
        return '' unless date?
        now = moment()
        time = moment(date)
        wasToday = time.date() is now.date() and Math.abs(time.diff(now)) < 86400000
        wasJustNow = Math.abs(time.diff(now)) < 120000
        """#{ time.from() } #{ if wasJustNow then '' else time.format('(' + (if wasToday then '' else 'l ') + 'h:mma)') }"""

module.exports = Utils

class Utils

    @getHTMLTitleFromHistoryFragment: (fragment) ->
        _.capitalize(fragment.split('\/').join(' '))

    @getShortTaskID: (taskId) ->
        split = taskId.split(/\-|\:|\./)
        if split.length > 1
            return "#{ split[0] }..."
        else
            return _.truncate(taskId, 20)
        return taskId

    @stringJSON: (object) ->
        JSON.stringify object, null, '    '

    @viewJSON: (type, objectId) ->
        lookupObject = {}

        if type is 'task'
            lookupObject = app.allTasks
        if type is 'request'
            lookupObject = app.allRequests

        vex.dialog.alert
            contentCSS:
                width: 800
            message: "<pre>#{ lookupObject[objectId].JSONString }</pre>"

    @setupSortableTables: ->
        sortable.init()

    @humanTimeAgo: (date) ->
        return '' unless date?
        now = moment()
        time = moment(date)
        wasToday = time.date() is now.date() and Math.abs(time.diff(now)) < 86400000
        wasJustNow = Math.abs(time.diff(now)) < 120000
        """#{ time.from() } #{ if wasJustNow then '' else time.format('(' + (if wasToday then '' else 'l ') + 'h:mma)') }"""

module.exports = Utils

Use _.string's `truncate` here instead

Use _.string's `truncate` here instead


helpers = (app) ->

  # dynamic helpers are given request and response as parameters
  app.dynamicHelpers
    flash: (req, res) -> req.flash()
    logged_in: -> true

  # static helpers take any parametes and usually format data
  app.helpers
    # Object is a mongoose model object
    urlFor: (object) ->
      if object.collection
        prefix = object.collection.name + '/'
      else
        prefix = ''

      unless object.isNew
        "/#{prefix}#{object.id}"
      else
        "/#{prefix}"

module.exports = helpers

helpers = (app) ->

  # dynamic helpers are given request and response as parameters
  app.dynamicHelpers
    flash: (req, res) -> req.flash()
    logged_in: -> true

  # static helpers take any parametes and usually format data
  app.helpers
    # Object is a mongoose model object
    urlFor: (object, path) ->
      if path
        prefix = path + '/'
      else if object.collection
        prefix = object.collection.name + '/'
      else
        prefix = ''

      unless object.isNew
        "/#{prefix}#{object.id}"
      else
        "/#{prefix}"

module.exports = helpers

Add the option to pass a path into the urlFor helper method


React = require 'react'
CropInitializer = require './initializer'
GenericTask = require '../generic'

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

  statics:
    getSVGProps: ({workflow, classification, annotation}) ->
      tasks = require '../index'
      [previousCropAnnotation] = classification.annotations.filter (anAnnotation) =>
        taskDescription = workflow.tasks[anAnnotation.task]
        TaskComponent = tasks[taskDescription.type]
        TaskComponent is this and anAnnotation.value? and anAnnotation isnt annotation
      if previousCropAnnotation?
        {x, y, width, height} = previousCropAnnotation.value
        viewBox: "#{x} #{y} #{width} #{height}"

    InsideSubject: CropInitializer

    getDefaultTask: ->
      type: 'crop'
      instruction: 'Drag to select the relevant area.'
      help: ''

    getTaskText: (task) ->
      task.instruction

    getDefaultAnnotation: ->
      value: null

    isAnnotationComplete: (task, annotation) ->
      annotation.value? or not task.required

  getDefaultProps: ->
    task: null
    annotation: null
    onChange: Function.prototype

  render: ->
    <GenericTask question={@props.task.instruction} help={@props.task.help}>
      <p>
        <button type="button" onClick={@handleClear}>Clear current crop</button>
      </p>
    </GenericTask>

  handleClear: ->
    @props.annotation.value = null
    @props.onChange()

React = require 'react'
CropInitializer = require './initializer'
GenericTask = require '../generic'

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

  statics:
    getSVGProps: ({workflow, classification, annotation}) ->
      tasks = require '../index'
      [previousCropAnnotation] = classification.annotations.filter (anAnnotation) =>
        taskDescription = workflow.tasks[anAnnotation.task]
        TaskComponent = tasks[taskDescription.type]
        TaskComponent is this and anAnnotation.value? and anAnnotation isnt annotation
      if previousCropAnnotation?
        {x, y, width, height} = previousCropAnnotation.value
        viewBox: "#{x} #{y} #{width} #{height}"

    InsideSubject: CropInitializer

    getDefaultTask: ->
      type: 'crop'
      instruction: 'Drag to select the relevant area.'
      help: ''

    getTaskText: (task) ->
      task.instruction

    getDefaultAnnotation: ->
      value: null

    isAnnotationComplete: (task, annotation) ->
      annotation.value? or not task.required

  getDefaultProps: ->
    task: null
    annotation: null
    onChange: Function.prototype

  render: ->
    <GenericTask question={@props.task.instruction} help={@props.task.help}>
      <p>
        <button type="button" disabled={not @props.annotation.value?} onClick={@handleClear}>Clear current crop</button>
      </p>
    </GenericTask>

  handleClear: ->
    @props.annotation.value = null
    @props.onChange()

Disable Clear button when there's no crop


marked = require 'marked'

renderer = new marked.Renderer()
renderer.code = ->
  ''
renderer.blockquote = ->
  ''
renderer.heading = ->
  ''
renderer.html = ->
  ''
renderer.image = ->
  ''
renderer.list = ->
  ''

markdown = (text) ->
  marked(text, renderer: renderer).replace(/<p>(.*)<\/p>/, "$1").trim()

module.exports =
  getSettingDescription: (keyPath) ->
    description = atom.config.getSchema(keyPath)?.description or ''
    markdown(description)

Make simple functions more tidy

:art: Make simple functions more tidy


class Lanes.Models.ChangeSet extends Lanes.Models.Base

    constructor: ->
        super
        this.created_at = new Date

    session:
        update: 'object'
        by: 'object'
        created_at: { type: 'date', setOnce: true }

    derived:
        record: { deps: ['collection'], fn: -> @collection.parent }
        record_name: { deps: ['record'], fn: -> _.field2title @record.api_path() }
        fields: { deps: ['update'], fn: -> _.keys(@update) }
        displayed_fields: { deps:['fields'], fn: -> _.without(@fields, 'updated_by_id', 'updated_at') }
        displayed_changes:
            deps: ['displayed_fields'], fn: ->
                _.map @displayed_fields, (field) =>
                    c = @update[field]
                    { name: field, from: c[0], to: c[1] }

    value: ->
        set = {}
        for field, change of @update
            set[field] = change[1]
        set

class ChangeSetCollection extends Lanes.Models.BasicCollection

    model: Lanes.Models.ChangeSet

    constructor: (options) ->
        super([], options)

    comparator: (a, b) ->
        if b.created_at < a.created_at then -1 else if b.created_at > a.created_at then 1 else 0

Lanes.Models.ChangeSet.Collection = ChangeSetCollection

class Lanes.Models.ChangeSet extends Lanes.Models.Base

    constructor: ->
        super
        this.created_at = new Date

    session:
        update: 'object'
        by: 'object'
        created_at: { type: 'date', setOnce: true }

    derived:
        record: { deps: ['collection'], fn: -> @collection.parent }
        record_name: { deps: ['record'], fn: -> _.field2title @record.api_path() }
        fields: { deps: ['update'], fn: -> _.keys(@update) }
        displayed_fields: { deps:['fields'], fn: -> _.without(@fields, 'updated_by_id', 'updated_at') }
        displayed_changes:
            deps: ['displayed_fields'], fn: ->
                _.map @displayed_fields, (field) =>
                    c = @update[field]
                    { name: field, from: c[0], to: c[1] }

    value: ->
        set = {}
        for field, change of @update
            set[field] = if _.isArray(change) then _.last(change) else change
        set

class ChangeSetCollection extends Lanes.Models.BasicCollection

    model: Lanes.Models.ChangeSet

    constructor: (options) ->
        super([], options)

    comparator: (a, b) ->
        if b.created_at < a.created_at then -1 else if b.created_at > a.created_at then 1 else 0

Lanes.Models.ChangeSet.Collection = ChangeSetCollection

Deal with change sets that are not arrays


# Description:
#   Listens for words and sometimes replies with an Arnie quote
#
# Dependencies:
#   None
#
# Commands:
#   None
#
# Author:
#   Casey Lawrence <cjlaw@users.noreply.github.com>
#

odds  = [1...100]

arnie_quotes = [
    'GET TO THE CHOPPA! :arnold:',
    'Your clothes, give them to me, now! :arnold:',
    'Hasta La Vista, Baby! :arnold:',
    'DDDAAANNNAAAA! :arnold:',
    'You are one ugly mother******. :arnold:',
    'It`s not a tumor! :arnold:',
    'When I said you should screw yourself. I didn`t mean it literally. :arnold:',
    'Can you hurry up. My horse is getting tired. :arnold:',
    'Are these all your lunches? You mean you eat other peoples` lunches? STOP IT!! :arnold:',
    'I`m the party pooper. :arnold:',
    'Who is your daddy and what does he do? :arnold:'
]

module.exports = (robot) ->
    robot.hear /(^|\s)arnie(\s|$|[\W])/ig, (msg) ->
        val = msg.random odds
        if val > 50
            msg.send msg.random arnie_quotes

    robot.hear /(^|\s)arnold(\s|$|[\W])/ig, (msg) ->
        val = msg.random odds
        if val > 50
            msg.send msg.random arnie_quotes

# Description:
#   Listens for words and sometimes replies with an Arnie quote
#
# Dependencies:
#   None
#
# Commands:
#   None
#
# Author:
#   Casey Lawrence <cjlaw@users.noreply.github.com>
#

odds  = [1...100]

arnie_quotes = [
    'GET TO THE CHOPPA!',
    'Your clothes, give them to me, now!',
    'Hasta La Vista, Baby!',
    'DDDAAANNNAAAA!',
    'You are one ugly mother******.',
    'It`s not a tumor!',
    'When I said you should screw yourself. I didn`t mean it literally.',
    'Can you hurry up. My horse is getting tired.',
    'Are these all your lunches? You mean you eat other peoples` lunches? STOP IT!!',
    'I`m the party pooper.',
    'Who is your daddy and what does he do?'
]

module.exports = (robot) ->
    robot.hear /(^|\s)arnie(\s|$|[\W])/ig, (msg) ->
        val = msg.random odds
        if val > 50
            msg.send msg.random arnie_quotes

    robot.hear /(^|\s)arnold(\s|$|[\W])/ig, (msg) ->
        val = msg.random odds
        if val > 50
            msg.send msg.random arnie_quotes

Remove the Arnold emoji since it's not displaying


Backbone = require 'backbone'
DropdownView = require '../../dropdown/client/dropdown_view.coffee'
mediator = require '../../../lib/mediator.coffee'

template = -> require('../templates/_dropdown_content.jade') arguments...

module.exports = class NotificationsView extends DropdownView

  initialize: ->
    super

    mediator.on 'notifications:synced', @maybeSetNotifications, @
    mediator.on 'notifications:synced', @renderFeed, @
    mediator.on 'notifications:cleared', @unsetNotifications, @

    @notifications = mediator.shared.notifications

  clearNotifications: (e)->
    @notifications.markRead()
    @$('.user-avatar').removeClass 'has-notifications'

  maybeSetNotifications: ->
    if (count = @notifications.getNumberUnread()) > 0
      @$('.notifications-menu__count').addClass '.notifications-menu__count--has-notifications'
      @$('.notifications-menu__count').text count

  unsetNotifications: ->
    @$('.user-avatar, .dropdown__link--notifications').removeClass '.notifications-menu__count--has-notifications'
    @$('.notifications-menu__count').text "0"

  renderFeed: ->
    @$('.dropdown__inner').html template
      feed: @notifications.models

Backbone = require 'backbone'
DropdownView = require '../../dropdown/client/dropdown_view.coffee'
mediator = require '../../../lib/mediator.coffee'

template = -> require('../templates/_dropdown_content.jade') arguments...

module.exports = class NotificationsView extends DropdownView

  initialize: ->
    super

    mediator.on 'notifications:synced', @maybeSetNotifications, @
    mediator.on 'notifications:synced', @renderFeed, @
    mediator.on 'notifications:cleared', @unsetNotifications, @

    @notifications = mediator.shared.notifications

  clearNotifications: (e)->
    @notifications.markRead()
    @unsetNotifications()

  openDropdown: ->
    super
    @clearNotifications()

  maybeSetNotifications: ->
    if (count = @notifications.getNumberUnread()) > 0
      @$('.notifications-menu__count').addClass 'notifications-menu__count--has-notifications'
      @$('.notifications-menu__count').text count

  unsetNotifications: ->
    @$('.notifications-menu__count').removeClass 'notifications-menu__count--has-notifications'
    @$('.notifications-menu__count').text "0"

  renderFeed: ->
    @$('.dropdown__inner').html template
      feed: @notifications.models

Fix notification class, clear on hover

Fix notification class, clear on hover


describe 'Logger', ->
  it 'expose window.log_event', ->
    expect(window.log_event).toBe Logger.log

  describe 'log', ->
    it 'send a request to log event', ->
      spyOn $, 'getWithPrefix'
      Logger.log 'example', 'data'
      expect($.getWithPrefix).toHaveBeenCalledWith '/event',
        event_type: 'example'
        event: '"data"'
        page: window.location.href

  # Broken with commit 9f75e64? Skipping for now.
  xdescribe 'bind', ->
    beforeEach ->
      Logger.bind()
      Courseware.prefix = '/6002x'

    afterEach ->
      window.onunload = null

    it 'bind the onunload event', ->
      expect(window.onunload).toEqual jasmine.any(Function)

    it 'send a request to log event', ->
      spyOn($, 'ajax')
      window.onunload()
      expect($.ajax).toHaveBeenCalledWith
        url: "#{Courseware.prefix}/event",
        data:
          event_type: 'page_close'
          event: ''
          page: window.location.href
        async: false

describe 'Logger', ->
  it 'expose window.log_event', ->
    expect(window.log_event).toBe Logger.log

  describe 'log', ->
    it 'send event to Segment.io if event is whitelisted', ->
      spyOn 'analytics.track'
      Logger.log 'seq_goto', 'data'
      expect(analytics.track).toHaveBeenCalledWith 'seq_goto', 'data'

    it 'send a request to log event', ->
      spyOn $, 'getWithPrefix'
      Logger.log 'example', 'data'
      expect($.getWithPrefix).toHaveBeenCalledWith '/event',
        event_type: 'example'
        event: '"data"'
        page: window.location.href

  # Broken with commit 9f75e64? Skipping for now.
  xdescribe 'bind', ->
    beforeEach ->
      Logger.bind()
      Courseware.prefix = '/6002x'

    afterEach ->
      window.onunload = null

    it 'bind the onunload event', ->
      expect(window.onunload).toEqual jasmine.any(Function)

    it 'send a request to log event', ->
      spyOn($, 'ajax')
      window.onunload()
      expect($.ajax).toHaveBeenCalledWith
        url: "#{Courseware.prefix}/event",
        data:
          event_type: 'page_close'
          event: ''
          page: window.location.href
        async: false

Add new Jasmine test to ensure Segment.io is called when appropriate


class Game.State extends Game.TwoWay
  constructor: () ->
    @objects = {}
    @eventCounters = {}
    super
  onEvent: (e) ->
    # Add timestamped instance to the event counters object
    if e.type not of @eventCounters
      @eventCounters[e.type] = []
    @eventCounters[e.type].push new Date()
    # relay the event upward
    @fire(e)
  addObject: (obj) ->
    if obj not instanceof Game.Object
      throw new Game.Error(Game.Error.ErrorType.NOT_OBJECT)
    if obj.getId() of @objects
      throw new Game.Error(Game.Error.ErrorType.ALREADY_ADDED)
    @objects[obj.getId()] = obj
    @listen(obj, @onEvent)
    # call object added method
    obj.added()

class Game.State extends Game.TwoWay
  constructor: () ->
    @objects = {}
    @eventCounters = {}
    @eventHandlers = {}
    super
  onEvent: (e) ->
    # Add timestamped instance to the event counters object
    if e.type not of @eventCounters
      @eventCounters[e.type] = []
    @eventCounters[e.type].push new Date()
    # relay the event upward
    @fire(e)
    # call added callbacks
    @eventHandlers[e.type].forEach (cb) -> cb()
    return
  addObject: (obj) ->
    if obj not instanceof Game.Object
      throw new Game.Error(Game.Error.ErrorType.NOT_OBJECT)
    if obj.getId() of @objects
      throw new Game.Error(Game.Error.ErrorType.ALREADY_ADDED)
    @objects[obj.getId()] = obj
    @listen(obj, @onEvent)
    # call object added method
    obj.added()
  addEventHandler: (evtType, callback) ->
    if evtType not of @eventHandlers
      @eventHandlers[evtType] = []
    if callback not in @eventHandlers[evtType]
      @eventHandlers[evtType].push callback
    return

Add custom event handlers to Game.State

Add custom event handlers to Game.State


class NCopyUrlView extends JView

  constructor: ->
    super

    @path = FSHelper.plainPath @getData().path
    @publicPath = @path.replace \
      ///.*\/(.*\.#{KD.config.userSitesDomain})\/(.*)///, 'http://$1/$2'

    @inputUrlLabel  = new KDLabelView
      cssClass      : 'public-url-label'
      title         : 'Public URL'
      click         :=>
        @focusAndSelectAll()

    @inputUrl       = new KDInputView
      label         : @inputUrlLabel
      cssClass      : 'public-url-input'

    @inputUrl.setValue @publicPath

  focusAndSelectAll:->
    @inputUrl.setFocus()
    @inputUrl.selectAll()

  viewAppended:->
    @setClass "copy-url-wrapper"
    super

  pistachio:->
    hasNoPublicPath = @publicPath is @path

    if hasNoPublicPath
      """

This #{@getData().type} can not be reached over a public URL

      """
    else
      """
      {{> @inputUrlLabel}}
      {{> @inputUrl}}
      """

class NCopyUrlView extends JView

  constructor: ->
    super

    @path = FSHelper.plainPath @getData().path
    @publicPath = @path.replace \
      ////home/(.*)/Web/(.*)///, "http://$1.#{KD.config.userSitesDomain}/$2"

    @inputUrlLabel  = new KDLabelView
      cssClass      : 'public-url-label'
      title         : 'Public URL'
      click         :=>
        @focusAndSelectAll()

    @inputUrl       = new KDInputView
      label         : @inputUrlLabel
      cssClass      : 'public-url-input'

    @inputUrl.setValue @publicPath

  focusAndSelectAll:->
    @inputUrl.setFocus()
    @inputUrl.selectAll()

  viewAppended:->
    @setClass "copy-url-wrapper"
    super

  pistachio:->
    hasNoPublicPath = @publicPath is @path

    if hasNoPublicPath
      """

This #{@getData().type} can not be reached over a public URL

      """
    else
      """
      {{> @inputUrlLabel}}
      {{> @inputUrl}}
      """

Fix copy file url issue

Fix copy file url issue


{sortLines} = require '../lib/sort-lines'

describe "SortLines", ->
  describe "sortLines(editor)", ->
    [editor, buffer] = []

    beforeEach ->
      editor = atom.project.openSync()
      buffer = editor.getBuffer()
      editor.setText """
        Hydrogen
        Helium
        Lithium
        Beryllium
        Boron
      """

    describe "when no lines are selected", ->
      it "sorts all lines", ->
        editor.setCursorBufferPosition([0, 0])
        sortLines(editor)
        expect(editor.getText()).toBe """
          Beryllium
          Boron
          Helium
          Hydrogen
          Lithium
        """

    describe "when entire lines are selected", ->
      it "sorts the selected lines", ->
        editor.setSelectedBufferRange([[1,0], [4,0]])
        sortLines(editor)
        expect(editor.getText()).toBe """
          Hydrogen
          Beryllium
          Helium
          Lithium
          Boron
        """

    describe "when partial lines are selected", ->
      it "sorts the selected lines", ->
        editor.setSelectedBufferRange([[1,3], [3,2]])
        sortLines(editor)
        expect(editor.getText()).toBe """
          Hydrogen
          Beryllium
          Helium
          Lithium
          Boron
        """

{sortLines} = require '../lib/sort-lines'

describe "SortLines", ->
  describe "sortLines(editor)", ->
    [editor] = []

    beforeEach ->
      editor = atom.project.openSync()
      editor.setText """
        Hydrogen
        Helium
        Lithium
        Beryllium
        Boron
      """

    describe "when no lines are selected", ->
      it "sorts all lines", ->
        editor.setCursorBufferPosition([0, 0])
        sortLines(editor)
        expect(editor.getText()).toBe """
          Beryllium
          Boron
          Helium
          Hydrogen
          Lithium
        """

    describe "when entire lines are selected", ->
      it "sorts the selected lines", ->
        editor.setSelectedBufferRange([[1,0], [4,0]])
        sortLines(editor)
        expect(editor.getText()).toBe """
          Hydrogen
          Beryllium
          Helium
          Lithium
          Boron
        """

    describe "when partial lines are selected", ->
      it "sorts the selected lines", ->
        editor.setSelectedBufferRange([[1,3], [3,2]])
        sortLines(editor)
        expect(editor.getText()).toBe """
          Hydrogen
          Beryllium
          Helium
          Lithium
          Boron
        """

Remove extraneous variable from specs

Remove extraneous variable from specs


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

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

Update Atom RuboCop Linter command path

Update Atom RuboCop Linter command path


Peer = require './peer'
Guid = require 'guid'

module.exports =
  createPeer: ->
    id = Guid.create().toString()
    new Peer(id, host: 'ec2-54-218-51-127.us-west-2.compute.amazonaws.com', port: 8080)

  connectDocument: (doc, connection) ->
    doc.outputEvents.on 'changed', (event) ->
      console.log 'sending event', event
      connection.send(event)

    connection.on 'data', (event) ->
      console.log 'receiving event', event
      doc.handleInputEvent(event)

Peer = require './peer'
Guid = require 'guid'

module.exports =
  createPeer: ->
    id = Guid.create().toString()
    new Peer(id, host: 'ec2-54-218-51-127.us-west-2.compute.amazonaws.com', port: 8080)

  connectDocument: (doc, connection) ->
    outputListener = (event) ->
      console.log 'sending event', event
      connection.send(event)
    doc.outputEvents.on('changed', outputListener)

    connection.on 'data', (event) ->
      console.log 'receiving event', event
      doc.handleInputEvent(event)

    connection.on 'close', ->
      doc.outputEvents.removeListener('changed', outputListener)

Remove output listener when connection is closed


Vue = require 'vue'
hljs = require 'highlight.js'
path = require 'path'
vmdl = require '../../src/vue-mdl'
require './style/mdl.scss'
require 'highlight.js/styles/tomorrow.css'
require 'material-design-lite/material.min.js'
context = require.context './partials', false, /.vue$/

vmdl.registerAll Vue

Vue.component 'title-link', require './utils/title-link.vue'

context.keys().forEach (comp) ->
  Vue.component path.basename(comp, '.vue'), context comp

Vue.config.debug = true
app = new Vue
  el: 'body'
  components:
    vmdlDoc: require './vmdl-doc.vue'

app.$nextTick -> hljs.initHighlightingOnLoad()

Vue = require 'vue'
hljs = require 'highlight.js/lib/highlight'
path = require 'path'
vmdl = require '../../src/vue-mdl'
require './style/mdl.scss'
require 'highlight.js/styles/tomorrow.css'
require 'material-design-lite/material.min.js'
context = require.context './partials', false, /.vue$/

vmdl.registerAll Vue

Vue.component 'title-link', require './utils/title-link.vue'

context.keys().forEach (comp) ->
  Vue.component path.basename(comp, '.vue'), context comp

Vue.config.debug = true
app = new Vue
  el: 'body'
  components:
    vmdlDoc: require './vmdl-doc.vue'

hljs.registerLanguage 'css', require 'highlight.js/lib/languages/css'
hljs.registerLanguage 'xml', require 'highlight.js/lib/languages/xml'
hljs.registerLanguage 'javascript', require 'highlight.js/lib/languages/javascript'
app.$nextTick -> hljs.initHighlightingOnLoad()

Include only what we need for syntax highlighting


class NFolderItemView extends NFileItemView

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

    options.cssClass  or= "folder"
    super options, data

    data.on "fs.chmod.finished", (recursive)=>
      warn "todo : refresh folder" if recursive

    if data.getExtension() is "kdapp"
      data.on "fs.delete.finished", =>
        @getSingleton("kodingAppsController").refreshApps()

class NFolderItemView extends NFileItemView

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

    options.cssClass  or= "folder"
    super options, data

    # data.on "fs.chmod.finished", (recursive)=>
    #   warn "todo : refresh folder" if recursive

    # FIXME GG Remove that here use watcher features instead
    {nickname} = KD.whoami().profile

    if data.type is 'folder' and (/\.kdapp$/.test data.path) or \
       ///#{nickname}\/Applications$///.test data.path

      data.on "fs.delete.finished", =>
        @getSingleton("kodingAppsController").refreshApps()

Refresh apps whenever Applications directory removed


# 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/

$(document).ready ->

  # Dynamic styling when clicking on the sidebar folders
  $(".menu-level1").click ->
    $(this).children("i.arrow").toggleClass "icon-chevron-right"
    $(this).children("i.arrow").toggleClass "icon-chevron-down"
    $(this).children("i.folder").toggleClass "icon-folder-close-alt"
    $(this).children("i.folder").toggleClass "icon-folder-open-alt"

  # Dynamid styling when clicking on a feed in the sidebar
  $("[data-feed]").click ->
    $("[data-feed]").parent().removeClass "active"
    $(this).parent().addClass "active"

  # Load new feed entries when clicking on the Refresh button
  $("[data-refresh]").click ->
    $("> i.icon-repeat", this).addClass "icon-spin"
    # Function to insert new entries in the list
    insert_entries = (entries) ->
      $("#feed-entries").prepend entries
      $("[data-refresh] > i.icon-repeat").removeClass "icon-spin"
    $.get "/feeds/1", null, insert_entries

# 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/

$(document).ready ->

##########################################################
# DYNAMIC STYLES
##########################################################

  # Dynamic styling when clicking on the sidebar folders
  $(".menu-level1").click ->
    $(this).children("i.arrow").toggleClass "icon-chevron-right"
    $(this).children("i.arrow").toggleClass "icon-chevron-down"
    $(this).children("i.folder").toggleClass "icon-folder-close-alt"
    $(this).children("i.folder").toggleClass "icon-folder-open-alt"

  # Dynamid styling when clicking on a feed in the sidebar
  $("[data-feed]").click ->
    $("[data-feed]").parent().removeClass "active"
    $(this).parent().addClass "active"

##########################################################
# AJAX
##########################################################

  # Load new feed entries when clicking on the Refresh button
  $("[data-refresh]").click ->
    $("> i.icon-repeat", this).addClass "icon-spin"
    # Function to insert new entries in the list
    insert_entries = (entries) ->
      $("[data-refresh] > i.icon-repeat").removeClass "icon-spin"
    $("#feed-entries").load "/feeds/1", null, insert_entries

Use jquery function load() instead of get() when refreshing a feed. Instead of inserting new new entries, the whole list of entries for the feed is inserted, replacing the previous list.

This has several advantages:

- cleaner code.
- ensure that when refreshing always the current list of entries is displayed, even if the user has several tabs/browsers open and is refreshing from different sessions.

The price to pay is probably a performance and bandwidth hit; instead of downloading the HTML for new entries, we download the HTML for the whole list of entries.

This also saves us one line of coffeescript code.


{CompositeDisposable} = require 'event-kit'

module.exports = (findAndReplace, minimapPackage) ->

  class MinimapFindResultsView

    constructor: (@model) ->
      @subscriptions = new CompositeDisposable
      @subscriptions.add @model.onDidUpdate @markersUpdated
      @decorationsByMarkerId = {}

    destroy: ->
      @subscriptions.dispose()
      @destroyDecorations()
      @decorationsByMarkerId = {}
      @markers = null

    destroyDecorations: ->
      decoration.destroy() for id, decoration of @decorationsByMarkerId

    getMinimap: -> minimapPackage.getActiveMinimap()

    markersUpdated: (markers) =>
      minimap = @getMinimap()
      return unless minimap?

      for marker in markers
        decoration = minimap.decorateMarker(marker, type: 'highlight', scope: '.minimap .search-result')
        @decorationsByMarkerId[marker.id] = decoration

    activePaneItemChanged: ->
      @destroyDecorations()
      setImmediate => @markersUpdated(@model.markers) if @markers?

{CompositeDisposable} = require 'event-kit'

module.exports = (findAndReplace, minimapPackage) ->

  class MinimapFindResultsView

    constructor: (@model) ->
      @subscriptions = new CompositeDisposable
      @subscriptions.add @model.onDidUpdate @markersUpdated
      @decorationsByMarkerId = {}

    destroy: ->
      @subscriptions.dispose()
      @destroyDecorations()
      @decorationsByMarkerId = {}
      @markers = null

    destroyDecorations: ->
      for id, decoration of @getMinimap().decorationsById
        if decoration.getProperties().scope is '.minimap .search-result'
          decoration.destroy()

    getMinimap: -> minimapPackage.getActiveMinimap()

    markersUpdated: (markers) =>
      minimap = @getMinimap()
      return unless minimap?

      for marker in markers
        decoration = minimap.decorateMarker(marker, type: 'highlight', scope: '.minimap .search-result')
        @decorationsByMarkerId[marker.id] = decoration

    activePaneItemChanged: ->
      @destroyDecorations()
      setImmediate => @markersUpdated(@model.markers) if @markers?

:bug: Fix markers persisting after disabling the plugin


angular.module("admin.enterprises", [
  "admin.paymentMethods",
  "admin.utils",
  "admin.shippingMethods",
  "admin.users",
  "textAngular",
  "admin.side_menu",
  "admin.taxons",
  'admin.indexUtils',
  'admin.tagRules',
  'admin.dropdown',
  'ngSanitize']
)
.config [
  '$provide', ($provide) ->
    $provide.decorator 'taTranslations', [
      '$delegate'
      (taTranslations) ->
        taTranslations.insertLink = {
          tooltip: 'Insert / edit link',
          dialogPrompt: "Please enter a URL to insert"
        }
        taTranslations
    ]
]

Add config for taTranslations (no functional change)


{$$, $$$, _, View} = require 'atom'

# Displays the keybindings for a package namespace
module.exports =
class PackageKeymapView extends View
  @content: ->
    @div class: 'package-keymap', =>
      @div class: 'package-keymap-heading icon icon-keyboard', 'Keybindings'
      @table outlet: 'keymapTable', class: 'package-keymap-table table', tabindex: -1, =>
        @thead =>
          @tr =>
            @th 'Keystroke'
            @th 'Command'
            @th 'Selector'
        @tbody outlet: 'keybindingItems'

  initialize: (namespace) ->
    for {command, keystroke, selector} in atom.keymap.getKeyBindings()
      continue unless command.indexOf("#{namespace}:") is 0

      @keybindingItems.append $$$ ->
        @tr class: 'package-keymap-item', =>
          @td class: 'keystroke', _.humanizeKeystroke(keystroke)
          @td class: 'command', command
          @td class: 'selector', selector

    @hide() unless @keybindingItems.children().length > 0

{$$, $$$, _, View} = require 'atom'

# Displays the keybindings for a package namespace
module.exports =
class PackageKeymapView extends View
  @content: ->
    @section class: 'package-keymap', =>
      @div class: 'section-heading package-keymap-heading icon icon-keyboard', 'Keybindings'
      @table outlet: 'keymapTable', class: 'package-keymap-table table', tabindex: -1, =>
        @thead =>
          @tr =>
            @th 'Keystroke'
            @th 'Command'
            @th 'Selector'
        @tbody outlet: 'keybindingItems'

  initialize: (namespace) ->
    for {command, keystroke, selector} in atom.keymap.getKeyBindings()
      continue unless command.indexOf("#{namespace}:") is 0

      @keybindingItems.append $$$ ->
        @tr class: 'package-keymap-item', =>
          @td class: 'keystroke', _.humanizeKeystroke(keystroke)
          @td class: 'command', command
          @td class: 'selector', selector

    @hide() unless @keybindingItems.children().length > 0

Use section element in package keymap view


# A model for a timelet
#
class Essence.Models.Timelet extends Backbone.Model
  localStorage: new Backbone.LocalStorage 'Timelets'
  defaults:
    running: false
    duration: 30
    name: 'My Timelet'

# A model for a timelet
#
class Essence.Models.Timelet extends Backbone.Model
  localStorage: new Backbone.LocalStorage 'Timelets'
  defaults:
    running: false
    duration: '--'
    name: 'New timelet'

Use default attributes that make it clear that it is a new record.


module.exports =
  initial:
    value: 'initial'
    slug: ''
    copy: 'Select Organization Type'
    type: null
  gallery:
    value: 'gallery'
    slug: 'gallery'
    copy: 'Gallery'
    type: 'Gallery'
  institution:
    value: 'institution'
    slug: 'institution'
    copy: 'Institution'
    type: 'Museum/Institution'
  fair:
    value: 'fair'
    slug: 'fair'
    copy: 'Fair'
    type: 'Art Fair'
  general:
    value: 'general'
    slug: 'general'
    copy: 'Other'
    type: 'Other'

module.exports =
  initial:
    value: 'initial'
    slug: ''
    copy: 'Select Organization Type'
    type: 'Other'
  gallery:
    value: 'gallery'
    slug: 'gallery'
    copy: 'Gallery'
    type: 'Gallery'
  institution:
    value: 'institution'
    slug: 'institution'
    copy: 'Institution'
    type: 'Museum/Institution'
  fair:
    value: 'fair'
    slug: 'fair'
    copy: 'Fair'
    type: 'Art Fair'
  general:
    value: 'general'
    slug: 'general'
    copy: 'Other'
    type: 'Other'

Send unset type as "Other"

Send unset type as "Other"


Vosae.TenantsShowView = Vosae.PageTenantView.extend
  classNames: ["outlet-tenants", "page-show-tenants"]

  didInsertElement: ->
    @_super()

    # If user already selected or tenant or has only one tenant
    # he will be automaticaly redirected to the tenant's dashboard
    preselectedTenant = @get 'controller.session.preselectedTenant'
    tenantsLength = Vosae.Tenant.all().get('length')
    if not preselectedTenant and tenantsLength != 1
      @get('controller.namespace').hideLoader()

Vosae.TenantsShowView = Vosae.PageTenantView.extend
  classNames: ["outlet-tenants", "page-show-tenants"]

  ###
    If user already selected or tenant or has only one tenant
    he will be automaticaly redirected to the tenant's dashboard,
    otherwise we hide the loader.
  ###
  checkTenants: (->
    preselectedTenant = @get 'controller.session.preselectedTenant'
    tenantsLength = Vosae.Tenant.all().get('length')
    if not preselectedTenant and tenantsLength != 1
      Vosae.Utilities.hideLoader()
  ).on "didInsertElement"

Fix a bug which prevent to see tenants because of the loader


React = require 'react'

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

  getDefaultProperties: ->
    task: null
    classification: null
    annotation: null

  render: ->

      {for identification, i in @props.annotation.value
        identification._key ?= Math.random()

        answersByQuestion = @props.task.questionsOrder.map (questionID) =>
          if questionID of identification.answers
            answerLabels = [].concat(identification.answers[questionID]).map (answerID) =>
              @props.task.questions[questionID].answers[answerID].label
            answerLabels.join ', '
        answersList = answersByQuestion.filter(Boolean).join '; '

            {@props.task.choices[identification.choice].label}
            {' '}
            <button type="button" className="survey-identification-remove" title="Remove" onClick={@handleRemove.bind this, i}>×</button>

          {' '}
        }

  handleRemove: (index) ->
    @props.annotation.value.splice index, 1
    @props.classification.update 'annotations'

React = require 'react'

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

  getDefaultProperties: ->
    task: null
    classification: null
    annotation: null

  render: ->

      {for identification, i in @props.annotation.value
        identification._key ?= Math.random()

        answersByQuestion = @props.task.questionsOrder.map (questionID) =>
          if questionID of identification.answers
            answerLabels = [].concat(identification.answers[questionID]).map (answerID) =>
              @props.task.questions[questionID].answers[answerID].label
            answerLabels.join ', '
        answersList = answersByQuestion.filter(Boolean).join '; '

            {@props.task.choices[identification.choice].label}
            {' '}
            <button type="button" className="survey-identification-remove" title="Remove" onClick={@handleRemove.bind this, i}>×</button>

          {' '}
        }

  handleRemove: (index) ->
    @props.annotation.value.splice index, 1
    @props.onChange @props.annotation

Remove classification.update from survey task


# 1. Don't define a test with null as one of the options
# 2. If you change a test's options, you must also change the test's name
# 3. Record your tests here: https://docs.google.com/a/circleci.com/spreadsheet/ccc?key=0AiVfWAkOq5p2dE1MNEU3Vkw0Rk9RQkJNVXIzWTAzUHc&usp=sharing
#
# You can add overrides, which will set the option if override_p returns true.

exports = this

exports.ab_test_definitions =
  a_is_a: [true, false]
  split_form: [true, false]
  button_copy: [true, false]

Revert "add ab tests for home button"

Revert "add ab tests for home button"

This reverts commit d9c43403f18cbcab5d508a2c493683fa285d51e2.


goog.provide 'app.songs.edit.react.Page'

goog.require 'goog.ui.Textarea'

class app.songs.edit.react.Page

  ###*
    @param {app.Routes} routes
    @constructor
  ###
  constructor: (routes) ->
    {div,form,input,textarea,a} = React.DOM

    @create = React.createClass

      render: ->
        div className: 'new-song',
          form null,
            input
              autoFocus: true
              name: 'name'
              placeholder: Page.MSG_SONG_NAME
              type: 'text'
            textarea
              name: 'chordpro'
              placeholder: Page.MSG_WRITE_LYRICS_HERE
              ref: 'chordpro'
          a
            href: 'http://linkesoft.com/songbook/chordproformat.html'
            target: '_blank'
          , Page.MSG_HOW_TO_WRITE_LYRICS

      componentDidMount: ->
        @chordproTextarea_ = new goog.ui.Textarea ''
        @chordproTextarea_.decorate @refs['chordpro'].getDOMNode()

      componentWillUnmount: ->
        @chordproTextarea_.dispose()

  @MSG_SONG_NAME: goog.getMsg 'Song name'
  @MSG_WRITE_LYRICS_HERE: goog.getMsg 'Write lyrics here'
  @MSG_HOW_TO_WRITE_LYRICS: goog.getMsg 'How to write lyrics'

goog.provide 'app.songs.edit.react.Page'

goog.require 'goog.ui.Textarea'

class app.songs.edit.react.Page

  ###*
    @param {app.Routes} routes
    @constructor
  ###
  constructor: (routes) ->
    {div,form,input,textarea,button,a} = React.DOM

    @create = React.createClass

      render: ->
        div className: 'new-song',
          form onSubmit: @onFormSubmit,
            input
              autoFocus: true
              name: 'name'
              placeholder: Page.MSG_SONG_NAME
              ref: 'name'
              type: 'text'
            textarea
              name: 'chordpro'
              placeholder: Page.MSG_WRITE_LYRICS_HERE
              ref: 'chordpro'
            button
              type: 'submit'
            , Page.MSG_CREATE_NEW_SONG
          a
            href: 'http://linkesoft.com/songbook/chordproformat.html'
            target: '_blank'
          , Page.MSG_HOW_TO_WRITE_LYRICS

      componentDidMount: ->
        @chordproTextarea_ = new goog.ui.Textarea ''
        @chordproTextarea_.decorate @refs['chordpro'].getDOMNode()

      componentWillUnmount: ->
        @chordproTextarea_.dispose()

      onFormSubmit: (e) ->
        e.preventDefault()
        @refs['name'].getDOMNode().focus()

  @MSG_SONG_NAME: goog.getMsg 'Song name'
  @MSG_WRITE_LYRICS_HERE: goog.getMsg 'Write lyrics here'
  @MSG_CREATE_NEW_SONG: goog.getMsg 'Create new song'
  @MSG_HOW_TO_WRITE_LYRICS: goog.getMsg 'How to write lyrics'

Add on new song submit handler.

Add on new song submit handler.


#= require jquery.autosize

#we're using jquery autosize rather than jquery elastic since this works
#even if initialized before the element is in the DOM.

Backbone.Factlink ||= {}
class Backbone.Factlink.TextAreaView extends Backbone.Marionette.ItemView
  template: 'generic/text_area'
  events:
    'click': 'focusInput'
    'keydown .typeahead': 'parseKeyDown'
    'keyup .typeahead': 'updateModel'

  triggers:
    'focus .typeahead': 'focus'
    'blur .typeahead': 'blur'

  templateHelpers: =>
    placeholder: @options.placeholder

  initialize: ->
    @bindTo @model, 'change', @updateHtml, this

  focusInput: -> @$inputField().focus()

  parseKeyDown: (e) ->
    eventHandled = false
    switch e.keyCode
      when 13 then @trigger 'return'
      when 40 then @trigger 'down'
      when 38 then @trigger 'up'
      when 27 then @trigger 'escape'
      else eventHandled = true

    unless eventHandled
      e.preventDefault()
      e.stopPropagation()

  updateModel: ->
    @model.set text: @$inputField().val()
  updateHtml: ->
    if @model.get('text') != @$inputField().val()
      @$inputField().val(@model.get('text')).trigger('autosize')

  enable: -> @$inputField().prop 'disabled', false
  disable: ->@$inputField().prop 'disabled', true

  $inputField: -> @$('.typeahead')
  onRender: ->
    @$el.find('textarea').autosize()

#= require jquery.autosize

#we're using jquery autosize rather than jquery elastic since this works
#even if initialized before the element is in the DOM.

Backbone.Factlink ||= {}
class Backbone.Factlink.TextAreaView extends Backbone.Marionette.ItemView
  template: 'generic/text_area'
  events:
    # 'click': 'focusInput'
    'keydown .typeahead': 'parseKeyDown'
    'keyup .typeahead': 'updateModel'

  triggers:
    'focus .typeahead': 'focus'
    'blur .typeahead': 'blur'

  templateHelpers: =>
    placeholder: @options.placeholder

  initialize: ->
    @bindTo @model, 'change', @updateHtml, this

  # focusInput: -> @$inputField().focus()

  parseKeyDown: (e) ->
    eventHandled = false
    switch e.keyCode
      when 13 then @trigger 'return'
      when 40 then @trigger 'down'
      when 38 then @trigger 'up'
      when 27 then @trigger 'escape'
      else eventHandled = true

    unless eventHandled
      e.preventDefault()
      e.stopPropagation()

  updateModel: ->
    @model.set text: @$inputField().val()
  updateHtml: ->
    if @model.get('text') != @$inputField().val()
      @$inputField().val(@model.get('text')).trigger('autosize')

  enable: -> @$inputField().prop 'disabled', false
  disable: ->@$inputField().prop 'disabled', true

  $inputField: -> @$('.typeahead')
  onRender: ->
    @$el.find('textarea').autosize append: '\n'

Tweak textarea input control; trimming unnecessary events, better layout when animating.


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

if !Settings.analytics?.postgres?
	module.exports =
		recordEvent: (user_id, event, metadata, callback = () ->) ->
			logger.log {user_id, event, metadata}, "no event tracking configured, logging event"
			callback()
else
	Sequelize = require "sequelize"
	options = _.extend {logging:false}, Settings.analytics.postgres

	sequelize = new Sequelize(
		Settings.analytics.postgres.database,
		Settings.analytics.postgres.username,
		Settings.analytics.postgres.password,
		options
	)

	Event = sequelize.define("Event", {
		user_id: Sequelize.STRING,
		event: Sequelize.STRING,
		metadata: Sequelize.JSON
	})

	module.exports =
		recordEvent: (user_id, event, metadata = {}, callback = (error) ->) ->
			if user_id? and typeof(user_id) != "string"
				user_id = user_id.toString()
			if user_id == Settings.smokeTest?.userId
				# Don't record smoke tests analytics
				return callback()
			Event
				.create({ user_id, event, metadata })
				.then(
					(result) -> callback(),
					(error) ->
						logger.err {err: error, user_id, event, metadata}, "error recording analytics event"
						callback(error)
				)

		sync: () -> sequelize.sync()

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

if !Settings.analytics?.postgres?
	module.exports =
		recordEvent: (user_id, event, segmentation, callback = () ->) ->
			logger.log {user_id, event, segmentation}, "no event tracking configured, logging event"
			callback()
else
	Sequelize = require "sequelize"
	options = _.extend {logging:false}, Settings.analytics.postgres

	sequelize = new Sequelize(
		Settings.analytics.postgres.database,
		Settings.analytics.postgres.username,
		Settings.analytics.postgres.password,
		options
	)

	Event = sequelize.define("Event", {
		user_id: Sequelize.STRING,
		event: Sequelize.STRING,
		segmentation: Sequelize.JSON
	})

	module.exports =
		recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) ->
			if user_id? and typeof(user_id) != "string"
				user_id = user_id.toString()
			if user_id == Settings.smokeTest?.userId
				# Don't record smoke tests analytics
				return callback()
			Event
				.create({ user_id, event, segmentation })
				.then(
					(result) -> callback(),
					(error) ->
						logger.err {err: error, user_id, event, segmentation}, "error recording analytics event"
						callback(error)
				)

		sync: () -> sequelize.sync()

Rename metadata -> segmentation in Events table to play well with metabase


BaseStackEditorView = require './basestackeditorview'

module.exports = class VariablesEditorView extends BaseStackEditorView

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

    super options, data

BaseStackEditorView = require './basestackeditorview'

module.exports = class VariablesEditorView extends BaseStackEditorView

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

    unless options.content
      options.content = """
        {
          "variables": {

          }
        }
      """

    super options, data

Set default content to variables editor

Set default content to variables editor


expect = require('./helpers/expect')()

Page = require './helpers/page'

describe 'E2E Testing Collection Detail', ->
  page = null

  beforeEach ->
    page = new Page '/quip/breast?project=QIN_Test'

  it 'should load the page', ->
    expect(page.content, 'The page was not loaded')
      .to.eventually.exist

#_ = require 'lodash'

#webdriver = require 'selenium-webdriver'

expect = require('./helpers/expect')()

Page = require './helpers/page'

describe 'E2E Testing Collection Detail', ->
  page = null

  before ->
    page = new collectionDetailPage()

  it 'should load the page', ->
    expect(page.content, 'The page was not loaded')
      .to.eventually.exist

  # The page header test cases.
  describe 'Header', ->
    it 'should display the billboard', ->
      expect(page.billboard, 'The billboard is incorrect')
        .to.eventually.equal('Breast Collection')

    it 'should have a home button', ->
      expect(page.home, 'The home URL is incorrect')
        .to.eventually.match(Page.HOME_URL_PAT)

    describe 'Help', ->
      help = null

      before ->
        help = page.help

      it 'should have help text', ->
        expect(help, 'The help text is missing')
          .to.eventually.exist.and.not.be.empty

      it 'should display a {qu,sugg}estion box hyperlink', ->
        expect(help, 'The {qu,sugg}estion box hyperlink is missing')
          .to.eventually.include(Page.SUGGESTION_BOX_URL)

    # The page main body test cases.
    describe 'Chart', ->
    	it 'should display the X-axis dropdowns', ->
        page.xAxisDropdowns.then (dropdowns) ->
          # The dropdowns exist.
          expect(dropdowns, 'The X-axis dropdowns are missing')
            .to.exist.and.not.be.empty
          # The dropdowns are displayed.
          for dropdown, i in dropdowns
            expect(dropdown.isDisplayed(),
                   "The X-axis dropdown #{ i } is initially hidden")
              .to.eventually.be.true

    	it 'should display the Y-axis dropdowns', ->
        page.yAxisDropdowns.then (dropdowns) ->
          # The dropdowns exist.
          expect(dropdowns, 'The Y-axis dropdowns are missing')
            .to.exist.and.not.be.empty
          # The dropdowns are displayed.
          for dropdown, i in dropdowns
            expect(dropdown.isDisplayed(),
                   "The Y-axis dropdown #{ i } is initially hidden")
              .to.eventually.be.true

      it 'should display the correlation charts', ->
        page.correlationCharts.then (charts) ->
          # The charts exist.
          expect(charts, 'The correlation charts are missing')
            .to.exist.and.not.be.empty
          # The charts are displayed.
          for chart, i in charts
            expect(chart.isDisplayed(),
                   "The correlation chart #{ i } is initially hidden")
              .to.eventually.be.true

Add collection detail E2E test cases.

Add collection detail E2E test cases.


React       = require('react/addons')
expect      = require('expect')
utils       = require('./utils')

TestUtils   = React.addons.TestUtils
ProgressBar = require('../progress_bar')

describe 'ProgressBar', ->
  describe '#props', ->
    it 'has the right default properties', ->
      progress = TestUtils.renderIntoDocument(<ProgressBar />)
      expect(progress.props.max).toEqual(100)
      expect(progress.props.min).toEqual(0)
      expect(progress.props.mode).toEqual('indeterminate')
      expect(progress.props.multicolor).toEqual(false)
      expect(progress.props.type).toEqual('linear')
      expect(progress.props.value).toEqual(0)

  describe '#calculateRadio', ->
    it 'calculates the right ratio', ->
      progress = TestUtils.renderIntoDocument(<ProgressBar min={100} max={300} />)
      expect(progress.calculateRatio(150)).toEqual(0.25)

  describe '#render', ->
    context 'when its a linear progress bar', ->
      it 'renders the value and buffer bar', ->
        progressWrapper = utils.shallowRenderComponent(ProgressBar).props.children
        expect(progressWrapper.props.children.length).toEqual(2)

Remove react explicit dependency in progressbar test


exists = require('fs').exists
app = module.exports = require('express')()
JFUM = require '../../src/index.coffee'
jfum = new JFUM()

app.options '/upload', jfum.optionsHandler.bind(jfum)
app.post '/upload', jfum.postHandler.bind(jfum), (req, res, next) ->
  console.log req.jfum
  return res.json foo: 'bar' if req.jfum is null

  exists req.jfum.file, (exist) ->
    console.log "File #{if exist then 'exists' else 'does not exist'}!"
    res.end()

app.listen 8080 if not module.parent

exists = require('fs').exists
app = module.exports = require('express')()
JFUM = require '../../src/index.coffee'
jfum = new JFUM
  minFileSize: 0
  maxFileSize: 10000000000
  acceptFileTypes: /\.(gif|jpe?g|png)$/i

app.options '/upload', jfum.optionsHandler.bind(jfum)
app.post '/upload', jfum.postHandler.bind(jfum), (req, res, next) ->
  console.log req.jfum
  return res.json foo: 'bar' if req.jfum is null

  exists req.jfum.file, (exist) ->
    console.log "File #{if exist then 'exists' else 'does not exist'}!"
    res.end()

app.listen 8080 if not module.parent

Add JFUM options to example

Add JFUM options to example


{div, button} = React.DOM
LeagueButton = React.createFactory @LeagueButton
PlayerTable = React.createFactory @PlayerTable
TeamTable = React.createFactory @TeamTable

@League = React.createClass
	getInitialState: ->
		activeView: "Player Stats"

	handleViewChange: (view) ->
		@setState activeView: view

	render: ->
		div className: "league row",
			div className: "row league--name",
				@props.league.name

			div className: "row league--buttons-container",
				div className: "small-4 columns league--buttons-gutter"
				div className: "small-4 columns league--buttons",

					LeagueButton
						onChange: @handleViewChange
						text: "Player Stats"

					LeagueButton
						onChange: @handleViewChange
						text: "Team Stats"

				div className: "small-4 columns league--buttons-gutter"

			div className: "league--content",
				if @state.activeView == "Player Stats"
					PlayerTable
						players: @props.player_metrics
				else
					TeamTable
						teams: _.sortBy(@props.team_metrics, (team) -> team["wins"]["value"]).reverse()

Fix indentation issues question mark

Fix indentation issues question mark


# Shared before/after functions for all integration tests

{wd40, browser} = require 'wd40'
cleaner = require '../cleaner'
request = require 'request'

base_url = 'http://localhost:3001'
login_url = "#{base_url}/login"

prepIntegration = ->
  before (done) ->
    cleaner.clear_and_set_fixtures ->
      wd40.init ->
        browser.get login_url, done

  after (done) ->
    unless process.env.BROWSER_KEEP?
      browser.quit done
    else
      done()

mediumizeMary = (done) ->
  # Ensures medium-mary starts on the right plan

  sub_uuid = '21cc59ce00cb05f2bbc397452a99369c' # medium-mary's subscription
  domain = process.env.RECURLY_DOMAIN
  pub_key = process.env.RECURLY_API_KEY

  request.put "https://#{domain}.recurly.com/v2/subscriptions/#{sub_uuid}",
    auth:
      user: pub_key
      pass: ''
    body: '<subscription><plan_code>medium-ec2</plan_code></subscription>'
  , done

exports.wd40 = wd40
exports.browser = browser
exports.base_url = base_url
exports.login_url = login_url
exports.home_url = "#{base_url}/datasets"
exports.prepIntegration = prepIntegration
exports.mediumizeMary = mediumizeMary

# Shared before/after functions for all integration tests

{wd40, browser} = require 'wd40'
cleaner = require '../cleaner'
request = require 'request'

test_server = process.env.CU_TEST_SERVER ? 'localhost'
base_url = "http://#{test_server}:3001"
login_url = "#{base_url}/login"

prepIntegration = ->
  before (done) ->
    cleaner.clear_and_set_fixtures ->
      wd40.init ->
        browser.get login_url, done

  after (done) ->
    unless process.env.BROWSER_KEEP?
      browser.quit done
    else
      done()

mediumizeMary = (done) ->
  # Ensures medium-mary starts on the right plan

  sub_uuid = '21cc59ce00cb05f2bbc397452a99369c' # medium-mary's subscription
  domain = process.env.RECURLY_DOMAIN
  pub_key = process.env.RECURLY_API_KEY

  request.put "https://#{domain}.recurly.com/v2/subscriptions/#{sub_uuid}",
    auth:
      user: pub_key
      pass: ''
    body: '<subscription><plan_code>medium-ec2</plan_code></subscription>'
  , done

exports.wd40 = wd40
exports.browser = browser
exports.base_url = base_url
exports.login_url = login_url
exports.home_url = "#{base_url}/datasets"
exports.prepIntegration = prepIntegration
exports.mediumizeMary = mediumizeMary

Use CU_TEST_SERVER environment variable when creating URL to contact custard on. Allows for running Selenium on a different computer.


mongoose = require 'mongoose'
Settings = require 'settings-sharelatex'

Schema = mongoose.Schema
ObjectId = Schema.ObjectId

SubscriptionSchema = new Schema
	admin_id     :   {type:ObjectId, ref:'User', index: {unique: true, dropDups: true}}
	member_ids   :   [ type:ObjectId, ref:'User' ]
	invited_emails:  [ String ]
	recurlySubscription_id : String
	planCode 	 : {type: String}
	groupPlan	 : {type: Boolean, default: false}
	membersLimit: {type:Number, default:0}
	customAccount: Boolean
	freeTrial:
		expiresAt: Date
		downgraded: Boolean
		planCode: String
		allowed: {type: Boolean, default: true}

SubscriptionSchema.statics.findAndModify = (query, update, callback)->
	self = @
	this.update query, update, ->
		self.findOne query, callback

conn = mongoose.createConnection(Settings.mongo.url, {
	server: {poolSize: Settings.mongo.poolSize || 10},
	config: {autoIndex: false}
})

Subscription = conn.model('Subscription', SubscriptionSchema)

mongoose.model 'Subscription', SubscriptionSchema
exports.Subscription = Subscription
exports.SubscriptionSchema = SubscriptionSchema

mongoose = require 'mongoose'
Settings = require 'settings-sharelatex'

Schema = mongoose.Schema
ObjectId = Schema.ObjectId

SubscriptionSchema = new Schema
	admin_id     :   {type:ObjectId, ref:'User', index: {unique: true, dropDups: true}}
	member_ids   :   [ type:ObjectId, ref:'User' ]
	invited_emails:  [ String ]
	recurlySubscription_id : String
	planCode 	 : {type: String}
	groupPlan	 : {type: Boolean, default: false}
	membersLimit: {type:Number, default:0}
	customAccount: Boolean
	freeTrial:
		expiresAt: Date
		downgraded: Boolean
		planCode: String
		allowed: {type: Boolean, default: true}
	overleaf:
		id: { type: Number }

SubscriptionSchema.statics.findAndModify = (query, update, callback)->
	self = @
	this.update query, update, ->
		self.findOne query, callback

conn = mongoose.createConnection(Settings.mongo.url, {
	server: {poolSize: Settings.mongo.poolSize || 10},
	config: {autoIndex: false}
})

Subscription = conn.model('Subscription', SubscriptionSchema)

mongoose.model 'Subscription', SubscriptionSchema
exports.Subscription = Subscription
exports.SubscriptionSchema = SubscriptionSchema

Add overleaf id to user schema

Add overleaf id to user schema


FS = require('fs')
module.exports =
  Save:->
    try
      Files = []
      ActiveEditor = atom.workspace.getActiveEditor()
      atom.workspace.eachEditor (editor)->
        File = editor.getPath()
        return unless File
        Files.push File
      CurrentFile = ActiveEditor && ActiveEditor.getPath() || null;
      localStorage.setItem('open-last-project',JSON.stringify({Path: atom.project.getPath(), Files: Files, CurrentFile: CurrentFile}))
  LoadProject:->
    LastProject = localStorage.getItem('open-last-project')
    return unless LastProject
    LastProject = JSON.parse(LastProject)
    atom.project.setPath LastProject.Path
    Promises = []
    LastProject.Files.forEach (file)->
      Promises.push new Promise (resolve)->
        FS.exists file, (Status)->
          return resolve() unless Status
          atom.workspace.open(file).then(resolve)
    Promise.all(Promises).then ->
      # Remove the empty pane
      atom.workspace.eachEditor (editor)->
        editor.destroy() unless editor.getPath()
      # Set the last active file
      return unless LastProject.CurrentFile
      FS.exists LastProject.CurrentFile, (Status)->
        return unless Status
        atom.workspace.open LastProject.CurrentFile

FS = require('fs')
module.exports =
  Save:->
    try
      Files = []
      ActiveEditor = atom.workspace.getActiveEditor()
      atom.workspace.eachEditor (editor)->
        File = editor.getPath()
        return unless File
        Files.push File
      CurrentFile = ActiveEditor && ActiveEditor.getPath() || null;
      localStorage.setItem('open-last-project',JSON.stringify({Path: atom.project.getPath(), Files: Files, CurrentFile: CurrentFile}))
  LoadProject:->
    LastProject = localStorage.getItem('open-last-project')
    return unless LastProject
    LastProject = JSON.parse(LastProject)
    return unless LastProject.Path
    atom.project.setPath LastProject.Path
    Promises = []
    LastProject.Files.forEach (file)->
      Promises.push new Promise (resolve)->
        FS.exists file, (Status)->
          return resolve() unless Status
          atom.workspace.open(file).then(resolve)
    Promise.all(Promises).then ->
      # Remove the empty pane
      atom.workspace.eachEditor (editor)->
        editor.destroy() unless editor.getPath()
      # Set the last active file
      return unless LastProject.CurrentFile
      FS.exists LastProject.CurrentFile, (Status)->
        return unless Status
        atom.workspace.open LastProject.CurrentFile

:bug: Fix for scenarios where the localStorage item has old version's value


Sprangular.directive 'addressSelection', ->
  restrict: 'E'
  templateUrl: 'addresses/selection.html'
  scope:
    address: '='
    addresses: '='
    countries: '='
  controller: ($scope) ->
    $scope.existingAddress = false

    $scope.$watch 'addresses', (addresses) ->
      return unless addresses

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

        $scope.existingAddress = true if found

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

      if $scope.existingAddress
        $scope.address = $scope.addresses[0]

Initialize $scope.address when switching to existing address mode


class TentStatus.MacAuth
  constructor: (@options) ->
    @options = _.extend {
      time: parseInt((new Date * 1) / 1000)
      nonce: Math.random().toString(16).substring(3)
    }, @options

  signRequest: =>
    request_string = @buildRequestString()
    signature = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(request_string, @options.mac_key))
    @options.request.setHeader('Authorization', @buildAuthHeader(signature))

  buildRequestString: (body=@options.body) =>
    [@options.time, @options.nonce, @options.request.method.toUpperCase(), @options.request.path, @options.request.host, @options.request.port, body, null].join("\n")

  buildAuthHeader: (signature) =>
    """
    MAC id="#{@options.mac_key_id}", ts="#{@options.time}", nonce="#{@options.nonce}", mac="#{signature}"
    """

class TentStatus.MacAuth
  constructor: (@options) ->
    @options = _.extend {
      time: parseInt((new Date * 1) / 1000)
      nonce: Math.random().toString(16).substring(3)
    }, @options

  signRequest: =>
    request_string = @buildRequestString()
    signature = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(request_string, @options.mac_key))
    @options.request.setHeader('Authorization', @buildAuthHeader(signature))

  buildRequestString: (body=@options.body) =>
    [@options.time, @options.nonce, @options.request.method.toUpperCase(), @options.request.path, @options.request.host, @options.request.port, null, null].join("\n")

  buildAuthHeader: (signature) =>
    """
    MAC id="#{@options.mac_key_id}", ts="#{@options.time}", nonce="#{@options.nonce}", mac="#{signature}"
    """

Remove body from mac signature

Remove body from mac signature


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

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

  searchUsers: (value, callback) ->
    unless value is ''
      apiClient.type('users').get display_name: value, page_size: 10
        .then (users) =>
          opts = for user in users
            { value: user.id, label: "@#{ user.login }: #{ user.display_name }" }
          callback null, {
            options: opts
          }

  render: ->
    <Select
      multi={true}
      name="userids"
      placeholder="Username:"
      searchPromptText="Type to Search Users"
      className="user-search"
      closeAfterClick={true}
      asyncOptions={debounce(@searchUsers, 200)} />

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

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

  searchUsers: (value, callback) ->
    unless value is ''
      apiClient.type('users').get display_name: "#{value}*", page_size: 10
        .then (users) =>
          opts = for user in users
            { value: user.id, label: "@#{ user.login }: #{ user.display_name }" }
          callback null, {
            options: opts
          }

  render: ->
    <Select
      multi={true}
      name="userids"
      placeholder="Username:"
      searchPromptText="Type to Search Users"
      className="user-search"
      closeAfterClick={true}
      asyncOptions={debounce(@searchUsers, 200)} />

Add asterisk to display_name to trigger search

I made a small backend change. This fixes user search for it


Reflux = require 'reflux'
{api} = require '../api/client'
config = require '../lib/config'
classifierActions = require '../actions/classifier-actions'

module.exports = Reflux.createStore
  subjects: []
  subject: null

  init: ->
    @next()
    @listenTo classifierActions.moveToNextSubject, @next

  getInitialState: ->
    @subjects
    @subject

  next: ->
    if @subjects.length == 0
      query =
        workflow_id: config.workflowId
        sort: 'queued'

      api.type('subjects').get query
        .then (subjects) =>
          return unless subjects.length > 0
          @subjects = subjects

        .then =>
          @loadSubjectImage()
    else
      @loadSubjectImage()

  loadSubjectImage: ->
    @subject = @subjects.shift()
    image = new Image()
    image.src = @subject.locations[0]["image/jpeg"].replace("http://zooniverse-export.s3-website-us-east-1.amazonaws.com", "https://zooniverse-export.s3.amazonaws.com")
    image.onload = =>
      @subject.srcWidth = image.naturalWidth
      @trigger @subject

Reflux = require 'reflux'
{api} = require '../api/client'
config = require '../lib/config'
classifierActions = require '../actions/classifier-actions'

module.exports = Reflux.createStore
  subjects: []
  subject: null

  init: ->
    @next()
    @listenTo classifierActions.moveToNextSubject, @next

  getInitialState: ->
    @subjects
    @subject

  next: ->
    if @subjects.length == 0
      query =
        workflow_id: config.workflowId
        sort: 'queued'

      api.type('subjects').get query
        .then (subjects) =>
          return unless subjects.length > 0
          @subjects = subjects

        .then =>
          @loadSubjectImage()
    else
      @loadSubjectImage()

  loadSubjectImage: ->
    @subject = @subjects.shift()
    @generateHttpsUrl()
    image = new Image()
    image.src = @subject.locations[0]['image/jpeg']
    image.onload = =>
      @subject.srcWidth = image.naturalWidth
      @trigger @subject

  generateHttpsUrl: ->
    # If it's not an https url, we replace the s3 domain name with a secure one
    url = @subject.locations[0]['image/jpeg']
    if url.substr(0, 5) != 'https'
      filename = url.replace /https?:\/\/[^\/]+/i, ''
      @subject.locations[0]['image/jpeg'] = 'https://zooniverse-export.s3.amazonaws.com' + filename

Replace resource URLs with https versions

This modifies the Subject resource to use an HTTPS url for the image location. Previously, it was doing a secure prefetch, but an insecure load on the classifier interface


{ DEMO_BLOCKS } = require('sharify').data
imagesLoaded = require 'imagesloaded'
slides = require './slides.coffee'
loggedOutNav = require '../../../components/logged_out_nav/client/index.coffee'
blockTemplate = ->
  require('../../../components/block_v2/templates/block.jade') arguments...

{ mountWithApolloProvider } = require '../../../react/apollo/index.js'
{ default: DescriptiveCarousel } = require '../../../react/components/DescriptiveCarousel/index.js'

module.exports = ->
  loggedOutNav()

  mountWithApolloProvider DescriptiveCarousel, { slides }, $('.js-home-carousel')

  $html = $('html, body')
  $el = $('.js-home')
  $sections = $el.find('.js-section')

  $el.find '.js-to-fold'
    .on 'click', (e) ->
      e.preventDefault()

      $target = $(this)
        .closest '.js-section'
        .next '.js-section'

      yPos = $target.position().top

      $html.animate scrollTop: yPos, 'fast'

{ DEMO_BLOCKS } = require('sharify').data
imagesLoaded = require 'imagesloaded'
{ default: Player } = require '@vimeo/player';

slides = require './slides.coffee'
loggedOutNav = require '../../../components/logged_out_nav/client/index.coffee'
blockTemplate = ->
  require('../../../components/block_v2/templates/block.jade') arguments...

{ mountWithApolloProvider } = require '../../../react/apollo/index.js'
{ default: DescriptiveCarousel } = require '../../../react/components/DescriptiveCarousel/index.js'

module.exports = ->
  loggedOutNav()

  mountWithApolloProvider DescriptiveCarousel, { slides }, $('.js-home-carousel')

  $el = $('.js-home')

  iframe = document.querySelector('iframe');
  player = new Player(iframe);

  $el.find '.js-to-fold'
    .on 'click', (e) ->
      $('.HomeHero__image > img').css({ opacity: 0 })
      $('.HomeHero__image__play').css({ opacity: 0 })
      $('.HomeHero__image__video').show()

      player.play()

Set up js for swapping video for image

Set up js for swapping video for image


define [
	"base"
], (App) ->
	App.controller "AutoCompileOnboardingController", ($scope) ->
		recompileBtn = angular.element('#recompile')
		popover = angular.element('.onboarding__autocompile')
		{ top, left } = recompileBtn.offset()

		# If pdf panel smaller than recompile button + popover, show to left.
		# Otherwise show to right
		if $scope.ui.pdfWidth < 475
			$scope.placement = 'left'
			popover.offset({
				top: top,
				left: left - popover.width()
			})
		else
			$scope.placement = 'right'
			angular.element('.onboarding__autocompile').offset({
				top: top,
				left: left + recompileBtn.width()
			})

		$scope.dismiss = () ->
			$scope.onboarding.autoCompile = 'dismissed'
			event_tracking.sendMB "shown-autocompile-onboarding"

Send analytics data when onboarding dismissed


window.ActiveService = class ActiveService
    constructor: ->
        @user = null

    setUser: (@user) ->
    getUser: -> @user

    isLoggedIn: -> !!(@user && @user.userId)

window.User = class User
    constructor: ->
        @userId = null
        @permissions = {}

    isAuthorized: (permission) ->
        if @permissions[permission]? and @permissions[permission] then true else false

    setFromApi: (options) ->
        @userId = options.user_id
        @username = options.username
        @

window.ActiveService = class ActiveService
    constructor: ->
        @user = null

    setUser: (@user) ->
    getUser: -> @user

    isLoggedIn: -> !!(@user && @user.userId)

window.User = class User
    constructor: ->
        @userId          = null
        @username        = null
        @name            = null
        @backgroundColor = null
        @permissions     = {}

    isAuthorized: (permission) ->
        if @permissions[permission]? and @permissions[permission] then true else false

    setFromApi: (options) ->
        @userId          = options.user_id
        @username        = options.username
        @name            = options.name
        @backgroundColor = options.background_color
        @

Add user's name and background color to User JS model


$ ->
  projects = $('#projects').clone()

  languages = []
  $('#languages a').click (e) ->
    clicked_language = $(this).data().language

    unless clicked_language?
      resetLanguage()
      return

    unless e.ctrlKey or e.metaKey
      languages = [].concat(clicked_language)
    else
      unless clicked_language in languages
        languages.push clicked_language
      else
        languages = (language for language in languages when language != clicked_language)

    $('#noprojects').hide()

    unless languages.length > 0
      resetLanguage()
    else
      $('#languages li')
        .addClass('disabled')
        .find($.map(languages, (l) -> "a[data-language=#{l}]").join(','))
        .parent('li')
        .removeClass('disabled')

      $('#projects').quicksand $(projects).find(".#{languages.join(', .')}"), duration: 0, ->
        if $('#projects .project:visible').length == 0
          $('#noprojects').show()

  resetLanguage = ->
      $('#languages li').removeClass('disabled')
      $('#projects').html(projects.find('.project'))
      projects = $('#projects').clone()
      $('#projects').css('height', 'auto')

$ ->
  projects = $('#projects').clone()

  languages = []
  $('#languages a').click (e) ->
    clicked_language = $(this).data().language

    unless clicked_language?
      resetLanguage()
      return false

    unless e.ctrlKey or e.metaKey
      languages = [].concat(clicked_language)
    else
      unless clicked_language in languages
        languages.push clicked_language
      else
        languages = (language for language in languages when language != clicked_language)

    $('#noprojects').hide()

    unless languages.length > 0
      resetLanguage()
    else
      $('#languages li')
        .addClass('disabled')
        .find($.map(languages, (l) -> "a[data-language=#{l}]").join(','))
        .parent('li')
        .removeClass('disabled')

      $('#projects').quicksand $(projects).find(".#{languages.join(', .')}"), duration: 0, ->
        if $('#projects .project:visible').length == 0
          $('#noprojects').show()
    return false

  resetLanguage = ->
      $('#languages li').removeClass('disabled')
      $('#projects').html(projects.find('.project'))
      projects = $('#projects').clone()
      $('#projects').css('height', 'auto')

Disable default link behaviour on project filter links


angular.module("hyperadmin")
  .controller "FormCtrl", ($scope, $state, Restangular, Flash) ->
    @resource = { }
    mode = $state.current.data.mode

    if mode == "new"
      method = "post"
      target = "admin/#{$scope.resourceClass.plural}"
      successMessage = "#{$scope.resourceClass.singular_human} created successfully."
    else
      method = "patch"
      target = "admin/#{$scope.resourceClass.plural}/#{$state.params.id}"
      successMessage = "#{$scope.resourceClass.singular_human} updated successfully."

      Restangular.one(target).get().then (resource) =>
        @resource = resource

    [ "id", "created_at", "updated_at" ].forEach (key) ->
      _.remove $scope.resourceClass.attributes, (attr) ->
        attr.key == key

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

      onSuccess = (resource) =>
        $state.go "^.show", id: resource.id
        Flash.setMessage "success", successMessage

      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, $state, Restangular, Flash) ->
    @resource = { }
    mode = $state.current.data.mode

    if mode == "new"
      method = "post"
      target = "admin/#{$scope.resourceClass.plural}"
      successMessage = "#{$scope.resourceClass.singular_human} created successfully."
    else
      method = "patch"
      target = "admin/#{$scope.resourceClass.plural}/#{$state.params.id}"
      successMessage = "#{$scope.resourceClass.singular_human} updated successfully."

      Restangular.one(target).get().then (resource) =>
        @resource = resource

        $scope.resourceClass.attributes.forEach (attr) =>
          if attr.type == "date" or attr.type == "datetime"
            if @resource[attr.key]
              @resource[attr.key] = new Date @resource[attr.key]

    [ "id", "created_at", "updated_at" ].forEach (key) ->
      _.remove $scope.resourceClass.attributes, (attr) ->
        attr.key == key

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

      onSuccess = (resource) =>
        $state.go "^.show", id: resource.id
        Flash.setMessage "success", successMessage

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

    this

Convert dates to actual JS dates for form


loggedOutNav = require '../../../components/logged_out_nav/client/index.coffee'

module.exports = ->
  $html = $('html, body')
  $el = $('.js-about')
  $links = $el.find('a[href*=#]')
  $sections = $el.find('.js-section[id]')

  loggedOutNav
    $sections: $sections

  scrollToId = (id) ->
    $target = $sections.filter("[id='#{id}']")

    offset = if ($page = $('.js-page')).length
      parseInt($page.css('margin-top'), 10) +
      parseInt($page.css('padding-top'), 10)
    else
      0

    yPos = $target.offset().top - offset

    $html.animate scrollTop: yPos, 'fast'

  $links
    .on 'click', (e) ->
      e.preventDefault()

      id = $(this).attr('href').split('#').pop()

      scrollToId id

  return unless location.hash

  # Prevent default anchor jump
  setTimeout (-> window.scrollTo(0, 0)), 1

  id = location.hash.substring(1)

  scrollToId id

loggedOutNav = require '../../../components/logged_out_nav/client/index.coffee'

module.exports = ->
  $html = $('html, body')
  $el = $('.js-about')
  $links = $el.find('a[href]')
  $sections = $el.find('.js-section[id]')

  loggedOutNav
    $sections: $sections

  scrollToId = (id) ->
    $target = $sections.filter("[id='#{id}']")

    offset = if ($page = $('.js-page')).length
      parseInt($page.css('margin-top'), 10) +
      parseInt($page.css('padding-top'), 10)
    else
      0

    yPos = $target.offset().top - offset

    $html.animate scrollTop: yPos, 'fast'

  $links
    .on 'click', (e) ->
      e.preventDefault()

      id = $(this).attr('href').split('#').pop()

      scrollToId id

  return unless location.hash

  # Prevent default anchor jump
  setTimeout (-> window.scrollTo(0, 0)), 1

  id = location.hash.substring(1)

  scrollToId id

Fix about page mobile menu selector

Fix about page mobile menu selector


App = window.App = Ember.Application.create()

App.ApplicationSerializer = DS.RESTSerializer.extend DS.EmbeddedRecordsMixin,

  keyForAttribute: (key, relationship) ->
    return Ember.String.capitalize(key)

  extract: (store, type, payload, id, requestType) ->
    normalizedPayload = {}
    normalizedPayload[Ember.String.pluralize(Ember.String.camelize(type.toString().split(".")[1]))] = payload.Entries
    @_super(store, type, normalizedPayload, id, requestType)

  normalize: (type, hash, prop) ->
    # Because FHIR resources have embedded stuff without IDs we're going to generate them
    hash.id ?= Em.generateGuid({}, type)
    @_super(type, hash, prop)
  serializeAttribute: (record, json, key, attribute) ->
    # Because of the way FHIR handles values we have to munge the JSON a little
    if key =="value"
      value = record.get('value')
      switch typeof value
        when "string"
          json.valuestring = value
        when "number"
          json.value = value
        when "object"
          json.valuerange = value
    else
      @_super(record, json, key, attribute)

# Order and include as you please.

require 'scripts/helpers'

require 'scripts/controllers/*'
require 'scripts/store'
require 'scripts/models/*'
require 'scripts/routes/*'
require 'scripts/components/*'
require 'scripts/views/*'
require 'scripts/router'

App = window.App = Ember.Application.create()

App.ApplicationSerializer = DS.RESTSerializer.extend DS.EmbeddedRecordsMixin,
  serializeIntoHash: (hash, type, record, options) ->
    Ember.merge(hash, this.serialize(record, options))

  keyForAttribute: (key, relationship) ->
    return Ember.String.capitalize(key)

  extract: (store, type, payload, id, requestType) ->
    normalizedPayload = {}
    normalizedPayload[Ember.String.pluralize(Ember.String.camelize(type.toString().split(".")[1]))] = payload.Entries
    @_super(store, type, normalizedPayload, id, requestType)

  normalize: (type, hash, prop) ->
    # Because FHIR resources have embedded stuff without IDs we're going to generate them
    hash.id ?= Em.generateGuid({}, type)
    @_super(type, hash, prop)
  serializeAttribute: (record, json, key, attribute) ->
    # Because of the way FHIR handles values we have to munge the JSON a little
    if key =="value"
      value = record.get('value')
      switch typeof value
        when "string"
          json.valuestring = value
        when "number"
          json.value = value
        when "object"
          json.valuerange = value
    else
      @_super(record, json, key, attribute)

# Order and include as you please.

require 'scripts/helpers'

require 'scripts/controllers/*'
require 'scripts/store'
require 'scripts/models/*'
require 'scripts/routes/*'
require 'scripts/components/*'
require 'scripts/views/*'
require 'scripts/router'

Change json payload to not have root element to align with what server expects.


urlparse = require 'url'

request = require '../request'
Media = require '../media'

exports.lookup = (id) ->
    return request.getJSON("https://api.twitch.tv/kraken/videos/#{id}").then((result) ->
        media = new Media(
            id: id
            title: result.title
            duration: result.length
            type: 'twitchvod'
            meta: {}
        )

        if result.thumbnails.length > 0
            media.meta.thumbnail = result.thumbnails[0].url

        return media
    )

exports.parseUrl = (url) ->
    m = url.match(/^tv:([cv]\d+)$/)
    if m
        return {
            type: 'twitchvod'
            kind: 'single'
            id: m[1]
        }

    data = urlparse.parse(url, true)

    if data.hostname not in ['www.twitch.tv', 'twitch.tv']
        return null

    m = data.pathname.match(/^\/(?:.*?)\/([cv])\/(\d+)/)
    if m
        return {
            type: 'twitchvod'
            kind: 'single'
            id: m[1] + m[2]
        }

    return null

urlparse = require 'url'

request = require '../request'
Media = require '../media'

CLIENT_ID = null

exports.lookup = (id) ->
    if not CLIENT_ID
        return Promise.reject(new Error('Client ID not set for Twitch API'))

    return request.getJSON("https://api.twitch.tv/kraken/videos/#{id}",
        headers:
            'Client-ID': CLIENT_ID
    ).then((result) ->
        media = new Media(
            id: id
            title: result.title
            duration: result.length
            type: 'twitchvod'
            meta: {}
        )

        if result.thumbnails.length > 0
            media.meta.thumbnail = result.thumbnails[0].url

        return media
    )

exports.parseUrl = (url) ->
    m = url.match(/^tv:([cv]\d+)$/)
    if m
        return {
            type: 'twitchvod'
            kind: 'single'
            id: m[1]
        }

    data = urlparse.parse(url, true)

    if data.hostname not in ['www.twitch.tv', 'twitch.tv']
        return null

    m = data.pathname.match(/^\/(?:.*?)\/([cv])\/(\d+)/)
    if m
        return {
            type: 'twitchvod'
            kind: 'single'
            id: m[1] + m[2]
        }

    return null

exports.setClientID = (clientID) ->
    CLIENT_ID = clientID

Add Client-ID header for Twitch API

Add Client-ID header for Twitch API


Crafty.c 'Mine',
  init: ->
    @requires 'Color, Enemy'

  drone: (attr = {}) ->
    @attr _.defaults(attr,
      w: 25, h: 25, health: 200)
    @origin 'center'
    @color '#999999'

    @enemy()
    this

Crafty.c 'Mine',
  init: ->
    @requires 'Color, Enemy'

  drone: (attr = {}) ->
    @attr _.defaults(attr,
      w: 25, h: 25, health: 200)
    @origin 'center'
    @color '#666666'

    @enemy()
    this

Make mine more visible in daylight

Make mine more visible in daylight