{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


# Centralizes configuration for currently running split tests
#
# eg.
# header_design:
#   key: 'header_design'
#   outcomes:
#     old: 8
#     new: 2
#
# Or, `outcomes` can be an array, when you specify `weighting: 'equal'.
#   weighting: 'equal'
#   outcomes: [
#     'old'
#     'new'
#   ]
#   edge: 'new'
#   dimension: 'dimension1' # Optional GA dimension
#   scope: 'local' # Optionally disable global initialization
#   control_group: 'old'  #Defaults to `control`, Reflection sees this.
#
# For equal weighting, add `weighting: 'equal'` and `outcomes` as an array.
#
# Note: if there are no running tests
# this should export empty Object
# module.exports = {}

module.exports = {
  artist_page_signup_modal:
    key: "artist_page_signup_modal"
    outcomes:
      control: 50
      experiment: 50
    control_group: "control"
    edge: "experiment"
}

# Centralizes configuration for currently running split tests
#
# eg.
# header_design:
#   key: 'header_design'
#   outcomes:
#     old: 8
#     new: 2
#
# Or, `outcomes` can be an array, when you specify `weighting: 'equal'.
#   weighting: 'equal'
#   outcomes: [
#     'old'
#     'new'
#   ]
#   edge: 'new'
#   dimension: 'dimension1' # Optional GA dimension
#   scope: 'local' # Optionally disable global initialization
#   control_group: 'old'  #Defaults to `control`, Reflection sees this.
#
# For equal weighting, add `weighting: 'equal'` and `outcomes` as an array.
#
# Note: if there are no running tests
# this should export empty Object
# module.exports = {}

module.exports = {
  artist_page_signup_modal:
    key: "artist_page_signup_modal"
    outcomes:
      control: 50
      experiment: 50
    control_group: "control"
    edge: "experiment"
  collection_hubs:
    key: "collection_hubs"
    outcomes:
      control: 100
      experiment: 0
    edge: 'experiment'
}

Set up A/B test for Collection Hubs (for now admin-only)

Signed-off-by: Myk Bilokonsky <75f49cdf1693dcb6c4be1fd9678ee266e958b7d5@gmail.com>


### define
../view/skeletontracing/abstract_tree_view : AbstractTreeView
###

class AbstractTreeController

  model : null
  view : null

  constructor : (@model) ->

    container = $("#abstractTreeViewer")
    @view = new AbstractTreeView(container.width(), container.height())
    container.append(@view.canvas)

    @bind()
    @drawTree(model.skeletonTracing.getTree())

  bind : ->

    @model.skeletonTracing.on({
      newActiveNode        : => @drawTree(),
      newActiveTree        : => @drawTree(),
      newTree              : => @drawTree(),
      mergeTree            : => @drawTree(),
      reloadTrees          : => @drawTree(),
      deleteTree           : => @drawTree(),
      deleteActiveNode     : => @drawTree(),
      newNode              : => @drawTree()
      })

  drawTree : ->

    @view.drawTree(@model.skeletonTracing.getTree(), @model.skeletonTracing.getActiveNodeId())

  setActiveNode : (nodeId, centered, mergeTree) ->

    { model } = @

    model.skeletonTracing.setActiveNode(nodeId, mergeTree)

    @centerActiveNode() if centered

  centerActiveNode : ->

    { model } = @

    position = model.skeletonTracing.getActiveNodePos()
    if position
      model.flycam.setPosition(position)

Remove trailing spaces, trigger jenkins

Remove trailing spaces, trigger jenkins


# Description:
#   Enable Hubot to post messages to Kato rooms using the HTTP API, allowing for messages with a coloured background and varying poster name.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   Emit event "kato-http-message" from your script, after setting room name-id matches in Hubot's brain with
#
#
# Notes:
#
#
# Author:
#   ocean

module.exports = (robot) ->
  robot.on 'kato-http-post', (kato-http-post) ->
    sendMessage kato-http-post

  robot.brain.data.kato-http-api.rooms or= {}

  sendMessage = (message) ->

    data = JSON.stringify({
      room: message.room || 'all',
      text: message.text,
      from: message.from || robot.name,
      color: message.color || 'grey',
      renderer: 'markdown'
    })

    roomURL = robot.brain.data.kato-http-api.rooms[data.room]

    robot.http(roomURL)
      .header('Content-Type', 'application/json')
      .post(data) (err, res, body) ->
        if err
          robot.emit 'error', err, res, body
          return
        robot.emit res, body

# Description:
#   Enable Hubot to post messages to Kato rooms using the HTTP API, allowing for messages with a coloured background and varying poster name.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   Emit event "kato-http-message" from your script, after setting Kato "All" room URL in environment variables.
#
#
# Notes:
#
#
# Author:
#   ocean

module.exports = (robot) ->

  config = secrets:
    katoRoomURL: process.env.HUBOT_KATO_ALL_ROOM_HTTP_POST_URL

  robot.on 'kato-http-post', (kato-http-post) ->
    sendMessage kato-http-post

  sendMessage = (message) ->

    data = JSON.stringify({
      text: message.text,
      from: message.from || robot.name,
      color: message.color || 'grey',
      renderer: 'markdown'
    })

    robot.http(config.secrets.katoRoomURL)
      .header('Content-Type', 'application/json')
      .post(data) (err, res, body) ->
        if err
          robot.emit 'error', err, res, body
          return
        robot.emit res, body

Set up environment variable to hold Kato "All" room HTTP POST URL.


angular.module("hyperadmin")
  .directive "formActions", ->
    template: """

    <md-button type="submit" class="md-primary md-raised"
      ng-disabled="form.$invalid">Submit</md-button>
    <md-button class="md-warn" ui-sref="^">Cancel

    """
    restrict: "E"
    scope:
      cancelState: "=cancelState"
      form: "=form"

Fix form button alignment issue

Fix form button alignment issue


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

$ ->
  animateMetric = $(".metric-box strong").each (index) ->
    $this = $(this)
    starting_point = 0
    $target_count = parseInt($this.text().replace(/\,/g, ''))

    # set the test to 0
    $this.counter = starting_point

    tween = createjs.Tween.get($this).to( {counter: $target_count}, (1000 + index * 1000) , createjs.Ease.quintOut)

    tween.addEventListener 'change', (event) ->
      $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
      return

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

roundUpToThousand = (value) ->
  return 1000 * Math.ceil(value / 1000)

$ ->
  animateMetric = $(".metric-box strong").each (index) ->
    $this = $(this)
    starting_point = 0
    $target_count = parseInt($this.text().replace(/\,/g, ''))

    # set the test to 0
    $this.counter = starting_point

    tween = createjs.Tween.get($this).to( {counter: $target_count}, (1000 + index * 1000) , createjs.Ease.quintOut)

    tween.addEventListener 'change', (event) ->
      # if the number is under 90 of the target, increment number by thousands
      $this.counter = roundUpToThousand($this.counter) if $this.counter < $target_count * 0.9
      $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
      return

Increment number by 1000s until almost at target


@ledger.wallet ?= {}

_.extend ledger.wallet,

  sweepPrivateKey: ({privateKey, account, txFee: 10000}, callback) ->
    completion = new CompletionClosure(callback)
    recipientAddress = account.getHDAccount().getCurrentPublicAddress()
    ecKey = new window.bitcoin.ECKey.fromWIF(privateKey)
    publicKey = ecKey.pub.getAddress().toString()
    addresses = [publicKey]

    ledger.api.UnspentOutputsRestClient.instance.getUnspentOutputsFromAddresses addresses , (outputs, error) ->
      return completion.failure(error) if error?

      txBuilder = new window.bitcoin.TransactionBuilder()
      amountToSend = new ledger.wallet.Value()

      _.async.each outputs, (output, done, hasNext) ->
        amountToSend = amountToSend.add output.value
        txBuilder.addInput(output.transaction_hash, output.output_index)

        if hasNext is false and amountToSend.lte(txFee)
          # Not enough available funds
          completion.failure(new ledger.StandardError(ledger.errors.NotEnoughFunds))
        else if hasNext is false
          amountToSend = amountToSend.subtract(10000)
          txBuilder.addOutput(recipientAddress, amountToSend.toNumber())

          txBuilder.sign(index, ecKey) for input, index in txBuilder.tx.ins
          txHex = txBuilder.build().toHex()
          ledger.api.TransactionsRestClient.instance.postTransactionHex txHex, (txHash, error) =>
            completion.complete(txHash, error)
        else
          # Continue to collect funds
          do done
    completion.readonly()

@ledger.wallet ?= {}

_.extend ledger.wallet,

  sweepPrivateKey: ({privateKey, account, txFee}, callback) ->
    txFee ?= 10000
    completion = new CompletionClosure(callback)
    recipientAddress = account.getHDAccount().getCurrentPublicAddress()
    ecKey = new window.bitcoin.ECKey.fromWIF(privateKey)
    publicKey = ecKey.pub.getAddress().toString()
    addresses = [publicKey]

    ledger.api.UnspentOutputsRestClient.instance.getUnspentOutputsFromAddresses addresses , (outputs, error) ->
      return completion.failure(error) if error?

      txBuilder = new window.bitcoin.TransactionBuilder()
      amountToSend = new ledger.wallet.Value()

      _.async.each outputs, (output, done, hasNext) ->
        amountToSend = amountToSend.add output.value
        txBuilder.addInput(output.transaction_hash, output.output_index)

        if hasNext is false and amountToSend.lte(txFee)
          # Not enough available funds
          completion.failure(new ledger.StandardError(ledger.errors.NotEnoughFunds))
        else if hasNext is false
          amountToSend = amountToSend.subtract(10000)
          txBuilder.addOutput(recipientAddress, amountToSend.toNumber())

          txBuilder.sign(index, ecKey) for input, index in txBuilder.tx.ins
          txHex = txBuilder.build().toHex()
          ledger.api.TransactionsRestClient.instance.postTransactionHex txHex, (txHash, error) =>
            completion.complete(txHash, error)
        else
          # Continue to collect funds
          do done
    completion.readonly()

Fix bad parameter in sweepPrivateKey

Fix bad parameter in sweepPrivateKey


$(".verified-status-tag").html "<%= escape_javascript(render :partial => 'shared/verified_status_tag', :formats => [ :html ]) %>"
$(".club-admin-status-text").html "<%= escape_javascript(render :partial => 'admin/club_live_status', :formats => [ :html ]) %>"
$(".club-promote-text").html "<%= escape_javascript(render :partial => 'admin/club_promote', :locals => { :club => @club }, :formats => [ :html ]) %>"
$(".modal").removeClass "paypal-confirm-modal" if $(".modal").hasClass("paypal-confirm-modal")
$(".modal").modal "hide"

Fix Admin PayPal Verified Tag Update

Fix Admin PayPal Verified Tag Update

Fix the Admin page to ensure that when a User verifies their account,
the corresponding PayPal verified tag in the header is updated.


define [
  'jquery'
  'underscore'
  'backbone'
], ($, _, Backbone) ->

  class Region
    constructor: (el, parent) ->
      @parent = parent
      @el = el

    show: (view) ->
      @$el = @$el or @parent.$el.find(@el)
      view.parent = @parent
      @view = view
      @view.setElement(@$el).render()

    close: () ->
      @view?.close()
      @$el.empty()

  class Regions
    constructor: (regions, $context) ->
      _.each _.keys(regions), (region) =>
        @[region] = new Region(regions[region], $context)

  return class BaseView extends Backbone.View
    initialize: () ->
      @regions = new Regions(@regions, @)

    render: () ->
      @$el.html(@template)

      return @

    close: () ->
      _.each @regions, (region) ->
        region.view.close()

      @stopListening()
      @remove()
      @unbind()

define [
  'jquery'
  'underscore'
  'backbone'
], ($, _, Backbone) ->

  class Region
    constructor: (el, parent) ->
      @parent = parent
      @el = el

    show: (view) ->
      @close()
      @append(view)

    append: (view) ->
      @$el = @$el or @parent.$el.find(@el).eq(0)
      view.parent = @parent
      @views = @views or []
      @views.push(view)
      view.setElement($('<div>').appendTo(@$el)).render()

    close: () ->
      _.each @views, (view) ->
        view.close()

      @$el?.empty()

  class Regions
    constructor: (regions, $context) ->
      _.each _.keys(regions), (region) =>
        @[region] = new Region(regions[region], $context)

  return class BaseView extends Backbone.View
    initialize: () ->
      @regions = new Regions(@regions, @)

    render: () ->
      @$el.html(@template)

      return @

    close: () ->
      _.each @regions, (region) ->
        region.close()

      @stopListening()
      @remove()
      @unbind()

Allow views to be appended

Allow views to be appended


run = () ->
	ReactDOM = require 'react-dom'
	cookie = require 'cookie'
	{ XmlEntities } = require 'html-entities'

	init = require './init.coffee'
	{ store } = require './store.coffee'
	userActions = require './actions/user-actions.coffee'
	routes = require './views/routes.coffee'
	createFactory = require './create-factory.coffee'
	Root = createFactory require './views/root.coffee'
	appActions = require './actions/app-actions.coffee'

	cookies = cookie.parse document.cookie
	if cookies.session
		session = JSON.parse atob cookies.session
		if session.user && session.timestamp
			store.dispatch userActions.setUser session.user, session.timestamp

	entities = new XmlEntities()
	serverState = document.getElementById('state').textContent
	data = JSON.parse entities.decode serverState
	store.dispatch appActions.init data
	state = store.getState()
	params = state.routing.state || {}
	init params, document.documentElement.lang
		.then ->
			ReactDOM.render Root(), document.getElementById 'app'
		.catch (err) ->
			console.error err.stack

if window.Promise && Object.assign
	run()
else
	require [ 'es6-promise', 'object-assign' ], (promiseLib, assign) ->
		window.Promise || (window.Promise = promiseLib.Promise)
		Object.assign || (Object.assign = assign)
		run()

require('es6-promise').polyfill()
assign = require 'object-assign'
ReactDOM = require 'react-dom'
cookie = require 'cookie'
{ XmlEntities } = require 'html-entities'

init = require './init.coffee'
{ store } = require './store.coffee'
userActions = require './actions/user-actions.coffee'
routes = require './views/routes.coffee'
createFactory = require './create-factory.coffee'
Root = createFactory require './views/root.coffee'
appActions = require './actions/app-actions.coffee'

Object.assign || (Object.assign = assign)

cookies = cookie.parse document.cookie
if cookies.session
	session = JSON.parse atob cookies.session
	if session.user && session.timestamp
		store.dispatch userActions.setUser session.user, session.timestamp

entities = new XmlEntities()
serverState = document.getElementById('state').textContent
data = JSON.parse entities.decode serverState
store.dispatch appActions.init data
state = store.getState()
params = state.routing.state || {}
init params, document.documentElement.lang
	.then ->
		ReactDOM.render Root(), document.getElementById 'app'
	.catch (err) ->
		console.error err.stack

Remove conditional polyfilling again. es6-promise should always be included since native Promise in Chrome is buggy. object-assign is so small it makes no sense to conditionally load on its own. fetch is a bit unfinished, and doesn't support cookies, so giving up on that for now.


Promise = require('bluebird')
canvas = require('./_canvas')

exports.getAll = ->
	return canvas.get
		resource: 'application'
		options:
			orderby: 'app_name asc'
			expand: 'device'

exports.get = (id) ->
	return canvas.get
		resource: 'application'
		id: id

	.then (application) ->
		if not application?
			Promise.reject(new Error('Not found'))

		return application

exports.create = (name, deviceType) ->
	return canvas.post
		resource: 'application'
		data:
			app_name: name
			device_type: deviceType

	.then (res) ->
		id = res?.id

		if not id?
			return Promise.reject(new Error('Could not find created application id.'))

		return id

exports.remove = (id) ->
	return canvas.delete
		resource: 'application'
		id: id

Promise = require('bluebird')
canvas = require('./_canvas')

exports.getAll = ->
	return canvas.get
		resource: 'application'
		options:
			orderby: 'app_name asc'
			expand: 'device'

exports.get = (id) ->
	return canvas.get
		resource: 'application'
		id: id

	.then (application) ->
		if not application?
			return Promise.reject(new Error('Not found'))

		return application

exports.create = (name, deviceType) ->
	return canvas.post
		resource: 'application'
		data:
			app_name: name
			device_type: deviceType

	.then (res) ->
		id = res?.id

		if not id?
			return Promise.reject(new Error('Could not find created application id.'))

		return id

exports.remove = (id) ->
	return canvas.delete
		resource: 'application'
		id: id

Return promise rejection to prevent Bluebird from complaining


define ['jquery', 'underscore', 'backbone', 'bootstrap', 'plugins/select_range'], ($, _, Backbone) ->
  class SearchInputView extends Backbone.View

    initialize: () ->
      @$el.typeahead
        source: @getTypeaheadAddresses
        updater: (item) =>
          _.defer @afterTypeahead
          item
        minLength: 3

    getTypeaheadAddresses: (query, callback) =>
      params = $.param { query: query }
      $.getJSON "/autocomplete?#{params}", (addresses) ->
        callback(addresses)

    afterTypeahead: () =>
      idx = @$el.val().lastIndexOf(',')
      @$el.selectRange(idx) if idx? and idx > 0

    indicateError: () ->
      @$el.closest('.control-group').addClass('error')

    clearError: () ->
      @$el.closest('.control-group').removeClass('error')

    validate: () ->
      if $.trim(@val()) is ''
        @indicateError()
        false
      else
        true

    focus: () =>
      @$el.focus()

    val: (v) =>
      if arguments.length > 0
        @$el.val.apply(@$el, arguments)
      else
        @$el.val()

    placeholder: (v) =>
      @originalPlaceholder ?= @$el.prop('placeholder')
      @$el.prop('placeholder', v)

    resetPlaceholder: () =>
      @$el.prop('placeholder', @originalPlaceholder)

define ['jquery', 'underscore', 'backbone', 'bootstrap', 'plugins/select_range'], ($, _, Backbone) ->
  class SearchInputView extends Backbone.View

    initialize: () ->
      @$el.typeahead
        source: @getTypeaheadAddresses
        updater: (item) =>
          _.defer @afterTypeahead
          item
        minLength: 3

    getTypeaheadAddresses: (query, callback) =>
      params = $.param { query: query }
      $.getJSON "/autocomplete?#{params}", (addresses) ->
        callback(addresses)

    afterTypeahead: () =>
      idx = @$el.val().lastIndexOf(',')
      @$el.selectRange(idx) if idx? and idx > 0

    indicateError: () ->
      @$el.closest('.control-group').addClass('error')
      this

    clearError: () ->
      @$el.closest('.control-group').removeClass('error')
      this

    validate: () ->
      if $.trim(@val()) is ''
        @indicateError()
        false
      else
        true

    focus: () =>
      @$el.focus()

    blur: ->
      @$el.blur()

    val: (v) =>
      if arguments.length > 0
        @$el.val.apply(@$el, arguments)
      else
        @$el.val()

    placeholder: (v) =>
      @originalPlaceholder ?= @$el.prop('placeholder')
      @$el.prop('placeholder', v)

    resetPlaceholder: () =>
      @$el.prop('placeholder', @originalPlaceholder)

Add blur method to SearchInputView.

Add blur method to SearchInputView.


gulp = require "gulp"

defaultTasks = [
  "scripts:minify:watch",
  "styles:minify:watch",
  "develop:watch",
]

gulp.task "default", defaultTasks, ->

gulp = require "gulp"

defaultTasks = [
  "scripts:watch",
  "scripts:minify:watch",
  "styles:minify:watch",
  "develop:watch",
]

gulp.task "default", defaultTasks, ->

Make sure that regular scripts are build too


`import DS from 'ember-data'`

Commit = DS.Model.extend
  remoteId: DS.attr('string')
  message: DS.attr('string')
  state: DS.attr('string')
  remoteUrl: DS.attr('string')
  project: DS.belongsTo('project')
  expiresAt: DS.attr('date')
  createdAt: DS.attr('date')
  authoredAt: DS.attr('date')
  author: DS.belongsTo('author')
  tickets: DS.hasMany('ticket')
  reviewer: DS.belongsTo('user', {async: true})
  fix: DS.hasMany('commit', {inverse: 'fix'})
  fixed: DS.hasMany('commit', {inverse: 'fixed'})

  shortHash: Ember.computed('remoteId', ->
    @get('remoteId').substr(0,6)
  )

  isRejected: Ember.computed('state', ->
    ['rejected', 'auto_rejected'].contains(@get('state'))
  )

  isPassed: Ember.computed('state', ->
    @get('state') == 'passed'
  )

  isFixed: Ember.computed('state', ->
    @get('state') == 'fixed'
  )

  isAccepted: Ember.computed('state', ->
    @get('state') == 'accepted'
  )

  belongsToMe: Ember.computed ->
    @store.find('user', 'me').get('person') is @get('author')

  hasReviewer: Ember.computed.notEmpty('reviewer.id')

`export default Commit`

`import DS from 'ember-data'`

Commit = DS.Model.extend
  remoteId: DS.attr('string')
  message: DS.attr('string')
  state: DS.attr('string')
  remoteUrl: DS.attr('string')
  project: DS.belongsTo('project')
  expiresAt: DS.attr('date')
  createdAt: DS.attr('date')
  authoredAt: DS.attr('date')
  author: DS.belongsTo('author')
  tickets: DS.hasMany('ticket')
  reviewer: DS.belongsTo('user', {async: true})
  fix: DS.hasMany('commit', {inverse: 'fix'})
  fixed: DS.hasMany('commit', {inverse: 'fixed'})

  shortHash: Ember.computed('remoteId', ->
    @get('remoteId').substr(0,6)
  )

  isRejected: Ember.computed('state', ->
    ['rejected', 'auto_rejected'].contains(@get('state'))
  )

  isPassed: Ember.computed('state', ->
    @get('state') == 'passed'
  )

  isFixed: Ember.computed('state', ->
    @get('state') == 'fixed'
  )

  isAccepted: Ember.computed('state', ->
    @get('state') == 'accepted'
  )

  hasExpiration: Ember.computed('state', ->
    @get('state') != 'fixed' and @get('state') != 'accepted'
  )

  belongsToMe: Ember.computed ->
    @store.find('user', 'me').get('person') is @get('author')

  hasReviewer: Ember.computed.notEmpty('reviewer.id')

`export default Commit`

Add computed property for expiration display

[#98105252]


ColorScanner = require '../lib/color-scanner'
{TextEditor} = require 'atom'

describe 'ColorScanner', ->
  [scanner, editor, bufferColor] = []

  withTextEditor = (fixture, block) ->
    describe "with #{fixture} buffer", ->
      beforeEach ->
        waitsForPromise -> atom.workspace.open(fixture)
        runs -> editor = atom.workspace.getActiveTextEditor()

      afterEach -> editor = null

      do block

  withScannerForTextEditor = (fixture, block) ->
    withTextEditor fixture, ->
      beforeEach -> scanner = new ColorScanner(buffer: editor.getBuffer())

      afterEach -> scanner = null

      do block

  describe '::search', ->
    withScannerForTextEditor 'four-variables.styl', ->
      beforeEach ->
        bufferColor = scanner.search()

      it 'returns the first buffer color match', ->
        expect(bufferColor).toBeDefined()

      describe 'successive searches', ->
        it 'returns a buffer color for each match and then undefined', ->
          expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeDefined()
          # expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeUndefined()

ColorScanner = require '../lib/color-scanner'
{TextEditor} = require 'atom'

describe 'ColorScanner', ->
  [scanner, editor, bufferColor] = []

  withTextEditor = (fixture, block) ->
    describe "with #{fixture} buffer", ->
      beforeEach ->
        waitsForPromise -> atom.workspace.open(fixture)
        runs -> editor = atom.workspace.getActiveTextEditor()

      afterEach -> editor = null

      do block

  withScannerForTextEditor = (fixture, block) ->
    withTextEditor fixture, ->
      beforeEach -> scanner = new ColorScanner(buffer: editor.getBuffer())

      afterEach -> scanner = null

      do block

  describe '::search', ->
    withScannerForTextEditor 'four-variables.styl', ->
      beforeEach ->
        bufferColor = scanner.search()

      it 'returns the first buffer color match', ->
        expect(bufferColor).toBeDefined()

      describe 'the resulting buffer color', ->
        it 'has a text range', ->
          expect(bufferColor.textRange).toEqual([8,12])

        it 'has a buffer range', ->
          expect(bufferColor.bufferRange).toEqual([[0, 8],[0, 12]])

        it 'has a color', ->
          expect(bufferColor.color).toBeColor('#ffffff')

        it 'stores the matched text', ->
          expect(bufferColor.match).toEqual('#fff')

      describe 'successive searches', ->
        it 'returns a buffer color for each match and then undefined', ->
          expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeDefined()
          # expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeUndefined()

Add tests for buffer color properties

:white_check_mark: Add tests for buffer color properties


class @WalletMessageIndexDialogViewController extends ledger.common.DialogViewController

  view:
    derivationPath: '#derivation_path'
    message: '#message'
    confirmButton: '#confirm_button'
    error: "#error_container"

  onAfterRender: ->
    super
    @_isEditable = @params.editable || no
    chrome.app.window.current().show()
    @view.derivationPath.val("m/" + @params.path)
    @view.message.val(@message)

    unless @_isEditable
      @view.message.attr("read-only", on)
      @view.derivationPath.attr("read-only", on)

  cancel: ->
    unless @_isEditable
      Api.callback_cancel 'sign_message', t('wallet.message.errors.cancelled')
    @dismiss()

  confirm: ->
    path = Api.cleanPath(@view.derivationPath.val())
    message = @view.message.val()
    if _.isEmpty(path) || path.match(/[^0-9\/']/ig)?
      @view.error.text(t("wallet.message.index.invalid_path"))
      return
    if _.isEmpty(message)
      @view.error.text(t("wallet.message.index.invalid_message"))
      return
    dialog = new WalletMessageProcessingDialogViewController(path: path, message: message, editable: @_isEditable)
    @getDialog().push dialog

class @WalletMessageIndexDialogViewController extends ledger.common.DialogViewController

  view:
    derivationPath: '#derivation_path'
    message: '#message'
    confirmButton: '#confirm_button'
    error: "#error_container"

  onAfterRender: ->
    super
    @_isEditable = @params.editable || no
    chrome.app.window.current().show()
    @view.derivationPath.val("m/" + Api.cleanPath(@params.path))
    @view.message.val(@params.message)
    unless @_isEditable
      @view.message.attr("readonly", on)
      @view.derivationPath.attr("readonly", on)

  cancel: ->
    unless @_isEditable
      Api.callback_cancel 'sign_message', t('wallet.message.errors.cancelled')
    @dismiss()

  confirm: ->
    path = Api.cleanPath(@view.derivationPath.val())
    message = @view.message.val()
    if _.isEmpty(path) || path.match(/[^0-9\/']/ig)?
      @view.error.text(t("wallet.message.index.invalid_path"))
      return
    if _.isEmpty(message)
      @view.error.text(t("wallet.message.index.invalid_message"))
      return
    dialog = new WalletMessageProcessingDialogViewController(path: path, message: message, editable: @_isEditable)
    @getDialog().push dialog

Fix Message signature via API

Fix Message signature via API


describe "Skr.Models.SalesOrder", ->

    xit "must have a customer to save", (done)->
        model = new Skr.Models.SalesOrder()
        Lanes.Test.ModelSaver.perform(model, done).then (save)->
            expect(save.error).toHaveBeenCalled()
            expect(model.errors.customer).toContain("is not set")

describe "Skr.Models.SalesOrder", ->

    it "it calculates total", ->
        so = new Skr.Models.SalesOrder()
        so.lines.add(sku_code: 'TEST', price: 2.12, qty:1)
        expect(so.total).toEqual(jasmine.any(_.bigDecimal))
        expect(so.total.toString()).toEqual('2.12')
        so.order_total = 1.42
        expect(so.total.toString()).toEqual('1.42')
        expect(so.total).toEqual(jasmine.any(_.bigDecimal))

    it 'updates total when lines change', ->
        so = new Skr.Models.SalesOrder()
        line = so.lines.add(sku_code: 'TEST', price: 2.12, qty:1)
        line.price = 3.42
        expect(so.total.toString()).toEqual('3.42')
        line.price = 233.111
        expect(so.total.toString()).toEqual('233.111')

Test that total is updated when lines change


angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
  return (products, hubPermissions, hub_id) ->
    return [] if !hub_id
    return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)

angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
  return (products, hubPermissions, hub_id) ->
    return [] if !hub_id
    return [] if !hubPermissions[hub_id]
    return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)

Add guard clause on variant overrides filter in case hub id is not present in the hubPermissions


Message = require './message'

class PanelView extends HTMLElement
  initialize:(@linter) ->
    @id = 'linter-panel'
    @_decorations = []

  empty: ->
    return unless @_decorations.length
    @_decorations.forEach (decoration) ->
      try decoration.destroy()
    @_decorations = []
    @innerHTML = ''

  update: ->
    if @linter.views.messages.length
      @render @linter.views.messages
      @linter.views.setPanelVisibility(true)
    else
      @hide()

  hide: ->
    @empty()
    @linter.views.setPanelVisibility(false)

  render: (messages) ->
    @empty()
    messages.forEach (message) =>
      if @linter.views.scope is 'file'
        return unless message.currentFile
      if message.currentFile and message.position
        p = message.position
        range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
        marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'line-number', class: "line-number-#{message.type.toLowerCase()}"
        )
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'highlight', class: "highlight-#{message.type.toLowerCase()}"
        )
      MessageLine = new Message()
      MessageLine.initialize(message, @linter.views.scope is 'project')
      @appendChild MessageLine

module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})

Message = require './message'

class PanelView extends HTMLElement
  initialize:(@linter) ->
    @id = 'linter-panel'

  render: (messages) ->
    messages.forEach (message) =>
      if @linter.views.scope is 'file'
        return unless message.currentFile
      if message.currentFile and message.position
        p = message.position
        range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
        marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'line-number', class: "line-number-#{message.type.toLowerCase()}"
        )
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'highlight', class: "highlight-#{message.type.toLowerCase()}"
        )
      MessageLine = new Message()
      MessageLine.initialize(message, @linter.views.scope is 'project')
      @appendChild MessageLine

module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})

Remove redundant part of PanelView

:fire: Remove redundant part of PanelView