class Written.Cursor
  constructor: (element, selection) ->
    @element = ->
      element
    @selection = selection
    children = Array.prototype.slice.call(@element().children, 0)
    @offset = selection.focusOffset

    node = selection.focusNode

    while node && !children.includes(node)
      parent = node.parentElement

      if parent
        child = node.previousSibling

        while child
          @offset += child.toString().length
          child = child.previousSibling

        if node.nodeName == 'LI'
          this.offset += Array.prototype.indexOf.call(parent.children, node)

      node = parent

    for child in @element().children
      if child == node
        break
      @offset += child.toString().length
      @offset += 1

    @currentNode = ->
      node

  offsetAt: (node) ->
    offset = @offset

    element = @element().firstElementChild
    while element && element != node
      offset -= element.toString().length
      element = element.nextElementSibling

    offset

  focus: (offset, node) =>
    if offset is undefined
      offset = @offset

    if node is undefined
      node = @element().firstElementChild

    while node.nextElementSibling && node.toString().length < offset
      offset -= node.toString().length + 1
      node = node.nextElementSibling

    range = node.getRange(Math.min(offset, node.toString().length), document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
    @selection.removeAllRanges()
    @selection.addRange(range)

class Written.Cursor
  constructor: (element, selection) ->
    @element = ->
      element
    @selection = selection
    children = Array.prototype.slice.call(@element().children, 0)
    @offset = selection.focusOffset

    node = selection.focusNode

    while node && !children.includes(node)
      parent = node.parentElement

      if parent
        child = node.previousSibling

        while child
          @offset += child.toString().length
          child = child.previousSibling

        if node instanceof HTMLLIElement
          this.offset += Array.prototype.indexOf.call(parent.children, node)

      node = parent

    for child in @element().children
      if child == node
        break
      @offset += child.toString().length
      @offset += 1

    @currentNode = ->
      node

  offsetAt: (node) ->
    offset = @offset

    element = @element().firstElementChild
    while element && element != node
      offset -= element.toString().length
      element = element.nextElementSibling

    offset

  focus: (offset, node) =>
    if offset is undefined
      offset = @offset

    if node is undefined
      node = @element().firstElementChild

    while node.nextElementSibling && node.toString().length < offset
      offset -= node.toString().length + 1
      node = node.nextElementSibling

    range = node.getRange(Math.min(offset, node.toString().length), document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
    @selection.removeAllRanges()
    @selection.addRange(range)

Use instanceof instead of nodeName for consistency's sake


# Include lib.
for file in ['buildable', 'arrayFilters', 'underscore']
  Ti.include('lib/' + file + '.js')

# Include expansions.
for file in ['citiesAndKnights']
  Ti.include('models/expansions/' + file + '.js')

# Include models.
for file in [
  'city',
  'game',
  'largestArmy',
  'longestRoad',
  'player',
  'settlement',
  'soldier',
  'developmentCardVictoryPoint',
  'gameSettings',
  'knight',
  'barbarians',
  'catanDefense',
  'barbariansBattleOutcome',
  'defenderOfCatanCard',
]
  Ti.include('models/' + file + '.js')

# Include lib.
for file in ['buildable', 'arrayFilters']
  Ti.include('lib/' + file + '.js')

# Include vendor.
for file in ['underscore']
  Ti.include('vendor/' + file + '.js')

# Include expansions.
for file in ['citiesAndKnights']
  Ti.include('models/expansions/' + file + '.js')

# Include models.
for file in [
  'city',
  'game',
  'largestArmy',
  'longestRoad',
  'player',
  'settlement',
  'soldier',
  'developmentCardVictoryPoint',
  'gameSettings',
  'knight',
  'barbarians',
  'catanDefense',
  'barbariansBattleOutcome',
  'defenderOfCatanCard',
]
  Ti.include('models/' + file + '.js')

Fix bug with including underscore.js.

Fix bug with including underscore.js.


Hummingbird.AnimeRoute = Ember.Route.extend
  model: (params) ->
    @store.find('fullAnime', params.id)

  saveLibraryEntry: (libraryEntry) ->
    anime = @currentModel
    Messenger().expectPromise (-> libraryEntry.save()),
      progressMessage: "Saving " + anime.get('canonicalTitle') + "..."
      successMessage: "Saved " + anime.get('canonicalTitle') + "!"

  actions:
    toggleFavorite: ->
      alert('Need to be signed in') unless @get('currentUser.isSignedIn')
      libraryEntry = @currentModel.get('libraryEntry')
      libraryEntry.set 'isFavorite', not libraryEntry.get('isFavorite')
      @saveLibraryEntry libraryEntry

    removeFromLibrary: ->
      anime = @currentModel
      libraryEntry = anime.get('libraryEntry')
      Messenger().expectPromise (-> libraryEntry.destroyRecord()),
        progressMessage: "Removing " + anime.get('canonicalTitle') + " from your library..."
        successMessage: "Removed " + anime.get('canonicalTitle') + " from your library!"

    setLibraryStatus: (newStatus) ->
      libraryEntry = @currentModel.get('libraryEntry')
      if libraryEntry
        libraryEntry.set 'status', newStatus
      else
        libraryEntry = @store.createRecord 'libraryEntry',
          status: newStatus
          anime: @currentModel
      @saveLibraryEntry libraryEntry

    setLibraryRating: (newRating) ->
      libraryEntry = @currentModel.get('libraryEntry')
      libraryEntry.set 'rating', newRating
      @saveLibraryEntry libraryEntry

Hummingbird.AnimeRoute = Ember.Route.extend
  model: (params) ->
    @store.find('fullAnime', params.id)

  saveLibraryEntry: (libraryEntry) ->
    anime = @currentModel
    Messenger().expectPromise (-> libraryEntry.save()),
      progressMessage: "Saving " + anime.get('canonicalTitle') + "..."
      successMessage: "Saved " + anime.get('canonicalTitle') + "!"

  actions:
    toggleFavorite: ->
      alert('Need to be signed in') unless @get('currentUser.isSignedIn')
      libraryEntry = @currentModel.get('libraryEntry')
      libraryEntry.set 'isFavorite', not libraryEntry.get('isFavorite')
      @saveLibraryEntry libraryEntry

    removeFromLibrary: ->
      anime = @currentModel
      libraryEntry = anime.get('libraryEntry')
      Messenger().expectPromise (-> libraryEntry.destroyRecord()),
        progressMessage: "Removing " + anime.get('canonicalTitle') + " from your library..."
        successMessage: "Removed " + anime.get('canonicalTitle') + " from your library!"

    setLibraryStatus: (newStatus) ->
      libraryEntry = @currentModel.get('libraryEntry')
      if libraryEntry
        libraryEntry.set 'status', newStatus
      else
        libraryEntry = @store.createRecord 'libraryEntry',
          status: newStatus
          anime: @currentModel
      if newStatus == "Completed"
        libraryEntry.set 'episodesWatched', libraryEntry.get('anime.episodeCount')
      @saveLibraryEntry libraryEntry

    setLibraryRating: (newRating) ->
      libraryEntry = @currentModel.get('libraryEntry')
      libraryEntry.set 'rating', newRating
      @saveLibraryEntry libraryEntry

Mark all episodes as viewed from anime pages.


behaviours = {}

share.attach = attach = (behaviour, args...) ->
  if _.isString behaviour
    options = behaviours[behaviour].options
    behaviour = behaviours[behaviour].behaviour

  if _.isFunction behaviour
    context =
      collection: @
      options: options or {}

    behaviour.apply context, args

  else
    console.warn 'Behaviour not found'

  return

class CollectionBehaviours

  @attach: (collection, args...) ->
    attach.apply collection, args

  @configure: (name, options) ->
    if name of behaviours
      behaviours[name].options = options

    else
      console.warn 'Configure failed, behaviour not found'

  @define: (name, behaviour, options) ->
    if name of behaviours and not options?.replace
      console.warn 'Behaviour already defined, use {replace: true} to override'

    else
      behaviours[name] =
        behaviour: behaviour

behaviours = {}

share.attach = attach = (behaviour, args...) ->
  if _.isString behaviour
    options = behaviours[behaviour].options
    behaviour = behaviours[behaviour].behaviour

  if _.isFunction behaviour
    context =
      collection: @
      options: options or {}

    behaviour.apply context, args

  else
    console.warn 'Behaviour not found'

  return

class CollectionBehaviours

  @attach: (collections, args...) ->
    if Match.test collections, Mongo.Collection
      collections = [collections]

    attach.apply collection, args for collection in collections

  @configure: (name, options) ->
    if name of behaviours
      behaviours[name].options = options

    else
      console.warn 'Configure failed, behaviour not found'

  @define: (name, behaviour, options) ->
    if name of behaviours and not options?.replace
      console.warn 'Behaviour already defined, use {replace: true} to override'

    else
      behaviours[name] =
        behaviour: behaviour

Support attaching behaviour to multiple collections


#= require ./abstract_attribute_binding

class Batman.DOM.EventBinding extends Batman.DOM.AbstractAttributeBinding
  onlyObserve: Batman.BindingDefinitionOnlyObserve.Data
  bindImmediately: false

  constructor: ->
    super

    callback = =>
      func = @get('filteredValue')
      target = @view.targetForKeypathBase(@functionPath)
      return func?.apply(target, arguments)

    if attacher = Batman.DOM.events[@attributeName]
      attacher(@node, callback, @view)
    else
      Batman.DOM.events.other(@node, @attributeName, callback, @view)

  _unfilteredValue: (key) ->
    if not @functionName and (index = key.lastIndexOf('.')) != -1
      @functionPath = key.substr(0, index)
      @functionName = key.substr(index + 1)
    else
      @functionPath = key

    value = super(@functionPath)
    if @functionName
      value?[@functionName]
    else
      value

#= require ./abstract_attribute_binding

class Batman.DOM.EventBinding extends Batman.DOM.AbstractAttributeBinding
  onlyObserve: Batman.BindingDefinitionOnlyObserve.Data
  bindImmediately: false

  constructor: ->
    super

    callback = =>
      func = @get('filteredValue')
      target = @view.targetForKeypathBase(@functionPath || @unfilteredKey)
      if target && @functionPath
        target = Batman.get(target, @functionPath)

      return func?.apply(target, arguments)

    if attacher = Batman.DOM.events[@attributeName]
      attacher(@node, callback, @view)
    else
      Batman.DOM.events.other(@node, @attributeName, callback, @view)

  _unfilteredValue: (key) ->
    @unfilteredKey = key
    if not @functionName and (index = key.lastIndexOf('.')) != -1
      @functionPath = key.substr(0, index)
      @functionName = key.substr(index + 1)

    value = super(@functionPath || key)
    if @functionName
      value?[@functionName]
    else
      value

Fix event bindings for variable-length keypaths for realsies.


compile = (template, type) ->
  if 0 <= index = template.indexOf ' '
    tmpl = template.substr 0, index
    attrs = template.substr index
  else
    tmpl = template

  [ name, classes... ] = tmpl.split /\.+/g
  if 0 is name.indexOf '#'
    id = name.substr 1
    name = 'div'

  if name is ''
    name = 'div'

  beginTag = "<#{name}"
  beginTag += " id='#{id}'" if id
  beginTag += " class='#{classes.join ' '}'" if classes.length
  beginTag += attrs if attrs
  beginTag += ">"
  closeTag = "</#{name}>"

  if type is '='
    (content) -> beginTag + (content or '') + closeTag
  else if type is '+'
    (content, arg0) -> #TODO add more args as necessary
      tag = replace beginTag, '{0}', arg0
      tag + content + closeTag
  else
    (contents) -> beginTag + (contents.join '') + closeTag

_templateCache = {}
Flow.HTML =
  template: (templates...) ->
    for template in templates
      if cached = _templateCache[template]
        cached
      else
        type = charAt template, 0
        if type is '=' or type is '+'
          _templateCache[template] = compile (template.substr 1), type
        else
          _templateCache[template] = compile template
  render: (name, html) ->
    el = document.createElement name
    if html
      if isString html
        el.innerHTML = html
      else
        el.appendChild html
    el

compile = (template, type) ->
  if 0 <= index = template.indexOf ' '
    tmpl = template.substr 0, index
    attrs = template.substr index
  else
    tmpl = template

  [ name, classes... ] = tmpl.split /\.+/g
  if 0 is name.indexOf '#'
    id = name.substr 1
    name = 'div'

  if name is ''
    name = 'div'

  beginTag = "<#{name}"
  beginTag += " id='#{id}'" if id
  beginTag += " class='#{classes.join ' '}'" if classes.length
  beginTag += attrs if attrs
  beginTag += ">"
  closeTag = "</#{name}>"

  if type is '='
    (content) -> beginTag + (if content isnt null and content isnt undefined then content else '') + closeTag
  else if type is '+'
    (content, arg0) -> #TODO add more args as necessary
      tag = replace beginTag, '{0}', arg0
      tag + content + closeTag
  else
    (contents) -> beginTag + (contents.join '') + closeTag

_templateCache = {}
Flow.HTML =
  template: (templates...) ->
    for template in templates
      if cached = _templateCache[template]
        cached
      else
        type = charAt template, 0
        if type is '=' or type is '+'
          _templateCache[template] = compile (template.substr 1), type
        else
          _templateCache[template] = compile template
  render: (name, html) ->
    el = document.createElement name
    if html
      if isString html
        el.innerHTML = html
      else
        el.appendChild html
    el

Handle zeros properly during templating

Handle zeros properly during templating


{ MEDIUM, CURRENT_USER } = require('sharify').data
AuthModalView = require '../../../components/auth_modal/view.coffee'
CTABarView = require '../../../components/cta_bar/view.coffee'

module.exports = (artist) ->
  return unless MEDIUM is 'search' and not CURRENT_USER?

  name = 'artist_cta'
  ctaHeadline = "Get updates on new shows and works by #{artist.get('name')}."
  modalCopy = "Get the latest new shows and works by #{artist.get('name')}."

  ctaBarView = new CTABarView
    headline: ctaHeadline
    name: name
    persist: true
    modalOptions:
      copy: modalCopy
      destination: "#{artist.href()}/follow"

  unless ctaBarView.previouslyDismissed()
    $('body').append ctaBarView.render().$el

    $('#artist-page-section-works').waypoint (direction) ->
      ctaBarView.transitionIn() if direction is 'down'
    , { offset: 'bottom-in-view' }

{ MEDIUM, CURRENT_USER } = require('sharify').data
AuthModalView = require '../../../components/auth_modal/view.coffee'
CTABarView = require '../../../components/cta_bar/view.coffee'

module.exports = (artist) ->
  return unless MEDIUM is 'search' and not CURRENT_USER?

  name = 'artist_cta'
  ctaHeadline = "Get updates on new shows and works by #{artist.get('name')}."
  modalCopy = "Get the latest new shows and works by #{artist.get('name')}."

  ctaBarView = new CTABarView
    headline: ctaHeadline
    name: name
    mode: ''
    persist: true
    modalOptions:
      copy: modalCopy
      destination: "#{artist.href()}/follow"

  unless ctaBarView.previouslyDismissed()
    $('body').append ctaBarView.render().$el

    $('#artist-page-section-works').waypoint (direction) ->
      ctaBarView.transitionIn() if direction is 'down'
    , { offset: 'bottom-in-view' }

Use correct CTA Bar on New Artist Pages

Use correct CTA Bar on New Artist Pages


storage = require 'node-persist'
storage.initSync()
config = storage.getItemSync 'config'

module.exports =
  getConfig: ->
    defaultDelay: 3
    defaultDuration: 0.5
    defaultTransition: "none"
    checkCycles: 20

Set check cycles to 2

Set check cycles to 2


Template.room_list.helpers
  rooms: ->
    searchQuery = Session.get 'search-query'
    query = {}
    query.displayName =  new RegExp searchQuery, 'i'
    Rooms.find query, { sort: { displayName: 1 } }

Template.room_list.rendered = ->
  @$('[data-toggle="tooltip"]').tooltip()
  @$('[data-toggle="popover"]').each ->
    $(@).popover
      placement: 'auto left'
      html: true
      container: 'body'
      content: ->
        $(@).parent().find('.meta').html()

Template.room_summary.helpers
  zoom: ->
    Session.get 'zoom'

Template.room_list.helpers
  rooms: ->
    searchQuery = Session.get 'search-query'
    query = {}
    query.displayName =  new RegExp searchQuery, 'i'
    Rooms.find query, { sort: { displayName: 1 } }

Template.room_list.rendered = ->
  @$('[data-toggle="tooltip"]').tooltip()

Template.room_summary.helpers
  zoom: ->
    Session.get 'zoom'

Template.rec.rendered = ->
  @$('[data-toggle="popover"]').each ->
    $(@).popover
      placement: 'auto left'
      html: true
      container: 'body'
      content: ->
        $(@).parent().find('.meta').html()

Fix rec popover behaviour when rec icon appears and page isn't reloaded.


{expect} = require "chai"
utils = require "../utils"

{Collections} = utils.require "common/base"

describe "categorical mapper", ->
  mapper = null
  before ->
    mapper = Collections('CategoricalMapper').create(
      source_range: Collections('FactorRange').create
        factors: ['foo', 'bar', 'baz']
      target_range: Collections('Range1d').create
        start: 20, 'end': 80
    )

  it "should map first category to bottom third", ->
    expect(mapper.map_to_target "foo").to.equal 30

  it "should map second category to middle", ->
    expect(mapper.map_to_target "bar").to.equal 50

  it "should map third category to upper third", ->
    expect(mapper.map_to_target "baz").to.equal 70

{expect} = require "chai"
utils = require "../utils"

{Collections} = utils.require "common/base"

describe "categorical mapper", ->
  factors = ["foo", "bar", "baz"]
  start = 20
  end = 80

  testMapping = (key, expected) ->
    mapper = Collections("CategoricalMapper").create
      source_range: Collections("FactorRange").create factors: factors
      target_range: Collections("Range1d").create start: start, end: end
    expect(mapper.map_to_target key).to.equal expected

  it "should map first category to bottom third", ->
    testMapping "foo", 30

  it "should map second category to middle", ->
    testMapping "bar", 50

  it "should map third category to upper third", ->
    testMapping "baz", 70

Refactor test case around to use a test method for each test


remote = require "remote"
dialog = remote.require "dialog"

module.exports =
class FileUtil
  constructor: () ->
  @prepareFile: (buffer) ->
    if buffer.file? && buffer.file.existsSync
      FileUtil.saveIfModified(buffer)
    else
      FileUtil.saveNewFile(buffer)

  @getPngFilePath:(file) ->
    pngFileName = FileUtil.getPngFilename(file)
    filePath = file.path.split('/')
    filePath.pop()
    filePath.join("/") + '/' + pngFileName

# private
  @saveIfModified:(buffer) ->
    if buffer.isModified()
      buffer.save()
    !buffer.isModified() # no longer modified when successfully saved

  @saveNewFile:(buffer) ->
    path = dialog.showSaveDialog(
      {options:{title:'save plantuml file'}})
    if path?
      buffer.setPath(path)
      buffer.save()
    !buffer.isModified() # no longer modified when successfully saved

  @getPngFilename:(file) ->
    fileName = path.basename(file.path)
    if fileName.indexOf('.') > -1
      unsuffixedFileName = fileName.split('.')
      unsuffixedFileName.pop()
      fileName = unsuffixedFileName.join('.')
    fileName + '.png'

remote = require "remote"
path   = require "path"
dialog = remote.require "dialog"

module.exports =
class FileUtil
  constructor: () ->
  @prepareFile: (buffer) ->
    if buffer.file? && buffer.file.existsSync
      FileUtil.saveIfModified(buffer)
    else
      FileUtil.saveNewFile(buffer)

  @getPngFilePath:(file) ->
    pngFileName = FileUtil.getPngFilename(file)
    filePath = file.path.split('/')
    filePath.pop()
    filePath.join("/") + '/' + pngFileName

# private
  @saveIfModified:(buffer) ->
    if buffer.isModified()
      buffer.save()
    !buffer.isModified() # no longer modified when successfully saved

  @saveNewFile:(buffer) ->
    path = dialog.showSaveDialog(
      {options:{title:'save plantuml file'}})
    if path?
      buffer.setPath(path)
      buffer.save()
    !buffer.isModified() # no longer modified when successfully saved

  @getPngFilename:(file) ->
    fileName = path.basename(file.path)
    if fileName.indexOf('.') > -1
      unsuffixedFileName = fileName.split('.')
      unsuffixedFileName.pop()
      fileName = unsuffixedFileName.join('.')
    fileName + '.png'

Fix error where path was not required

Fix error where path was not required


module.exports = (observed, keys, callback) ->
    Object.observe observed, (changes) =>
      for change in changes
        if change.name in keys
          callback()
          break

module.exports = (observed, keys, callback) ->
    Object.observe observed, (changes) =>
      for change in changes
        if (change.name in keys) or (keys.length is 0)
          callback()
          break

Allow observing [] for all keys

Allow observing [] for all keys


_ = require('underscore')
YAML = require('libyaml')
colors = require('colors')
fs = require('fs')
commander = require('commander')

proxy = require('./proxy')

commander
  .usage("Run it in the root of your project to start proxying requests as defined in the project's .vee file")
  .option('-p, --port [port number]', "port to run from", 80)
  .option('-d, --debug', "Output route matching debug info", false)
  .parse(process.argv)

options = _.pick commander, 'port'

try
  cfg = fs.readFileSync('.vee').toString('utf8')
catch e
  if e.code is 'ENOENT'
    console.error ".vee configuration file not found in the current directory".red
    process.exit(1)
  else
    throw e

try
  project = YAML.parse(cfg)[0]
catch e
  console.error ".vee file is not valid YAML: #{ e.toString() }".red
  process.exit(1)

options.routes = project.routes

proxy.start options

console.log "Proxy started on port #{ options.port }!".green

_ = require('underscore')
YAML = require('libyaml')
colors = require('colors')
fs = require('fs')
commander = require('commander')

proxy = require('./proxy')

commander
  .usage("Run it in the root of your project to start proxying requests as defined in the project's .vee file")
  .option('-p, --port [port number]', "port to run from", 80)
  .option('-d, --debug', "Output route matching debug info", false)
  .parse(process.argv)

options = _.pick commander, 'port', 'debug'

try
  cfg = fs.readFileSync('.vee').toString('utf8')
catch e
  if e.code is 'ENOENT'
    console.error ".vee configuration file not found in the current directory".red
    process.exit(1)
  else
    throw e

try
  project = YAML.parse(cfg)[0]
catch e
  console.error ".vee file is not valid YAML: #{ e.toString() }".red
  process.exit(1)

options.routes = project.routes

proxy.start options

console.log "Proxy started on port #{ options.port }!".green

Add missing debug pass through

Add missing debug pass through


Backbone.Factlink ||= {}

Backbone.Factlink.AddModelToCollectionMixin =
  addModel: (model) ->
    @options.addToCollection.add(model)
    model.save {},
      success: =>
        @addModelSuccess(model) if @addModelSuccess
      error: =>
        @options.addToCollection.remove(model)
        @addModelError(model) if @addModelError

  addDefaultModel: -> @addModel @model

  addWrappedModel: -> @addModel @wrapNewModel(@model)

Backbone.Factlink ||= {}

Backbone.Factlink.AddModelToCollectionMixin =
  addModel: (model, options) ->
    @options.addToCollection.add(model, options)
    model.save {},
      success: =>
        @addModelSuccess(model) if @addModelSuccess
      error: =>
        @options.addToCollection.remove(model)
        @addModelError(model) if @addModelError

  addDefaultModel: (options) -> @addModel @model, options

  addWrappedModel: -> @addModel @wrapNewModel(@model)

Add options to the AddModelToCollection addDefaultModel.


window.slackReadyFunction = (func) ->
  TS.groups.message_received_sig.add func
  TS.channels.message_received_sig.add func
  TS.ims.message_received_sig.add func
  TS.channels.switched_sig.add func
  TS.groups.switched_sig.add func
  TS.ims.switched_sig.add func
  func()

# 🙈
slackRebuildMsg = TS.client.msg_pane.rebuildMsg
TS.client.msg_pane.rebuild_sig = new signals.Signal
TS.client.msg_pane.rebuildMsg = ->
  slackRebuildMsg()
  TS.client.msg_pane.rebuild_sig.dispatch()

window.slackReadyFunction = (func) ->
  TS.client.msg_pane.rebuild_sig.add func

  for channel_type in ["groups", "channels", "ims", "mpims"]
    TS[channel_type].message_received_sig.add func
    TS[channel_type].switched_sig.add func
    TS[channel_type].history_fetched_sig.add func

  func()

Fix a few cases that caused us to lose our lovely hacks


Promise = require 'bluebird-q'
{ delay } = require 'underscore'
Backbone = require 'backbone'
template = -> require('./index.jade') arguments...

module.exports = class NotificationsView extends Backbone.View
  events:
    'click a': -> delay (=> @render()), 250

  initialize: ({ @state }) ->
    @listenTo @collection, 'sync', @render
    @listenTo @state, 'change:unread_count', @render
    @listenTo @state, 'change:is_fetching', @render

    @$el
      .one 'mouseenter', =>
        @state.set('is_fetching', true)
        Promise(@collection.fetch())
          .then =>
            @collection.clear()
            @state.set('is_fetching', false)

      .on 'mouseleave click', (e) =>
        @state.set('unread_count', 0)
        @collection.markRead()

  render: ->
    @$el.html template
      feed: @collection
      unread_count: @state.get('unread_count')
      is_fetching: @state.get('is_fetching')

    this

Promise = require 'bluebird-q'
{ delay } = require 'underscore'
Backbone = require 'backbone'
template = -> require('./index.jade') arguments...

module.exports = class NotificationsView extends Backbone.View
  events:
    'click a': -> delay (=> @render()), 250

  initialize: ({ @state }) ->
    @listenTo @collection, 'sync', @render
    @listenTo @state, 'change:unread_count', @render
    @listenTo @state, 'change:is_fetching', @render

    @$el
      .one 'mouseenter', =>
        @state.set('is_fetching', true)
        Promise(@collection.fetch())
          .then =>
            @collection.clear()
            @state.set('is_fetching', false)

      .on 'mouseleave click', (e) =>
        @collection.markRead()
        @state.set('unread_count', 0)

  render: ->
    @$el.html template
      feed: @collection
      unread_count: @state.get('unread_count')
      is_fetching: @state.get('is_fetching')

    this

Mark notifications as read first and then set the unread count


mongoose = require 'mongoose'
db = mongoose.connection

TaskSet = require './models/taskset'

saveCompletedTaskset = (tasksetData, callback) ->
  tasksetData.lock = false
  tasksetData.time.submit = new Date()
  tasksetData.time.workTime = tasksetData.time.submit - new Date(tasksetData.time.start)

  taskset = new TaskSet(tasksetData)
  TaskSet.findByIdAndUpdate(tasksetData._id, tasksetData, {upsert:true}, (err, taskset) ->
    callback(err, taskset)
    )

db.close()

module.exports = saveCompletedTaskset

mongoose = require 'mongoose'
db = mongoose.connection

TaskSet = require './models/taskset'

saveCompletedTaskset = (tasksetData, callback) ->
  tasksetData.lock = false
  tasksetData.status = 'reviewable'
  tasksetData.time.submit = new Date()
  tasksetData.time.workTime = tasksetData.time.submit - new Date(tasksetData.time.start)

  taskset = new TaskSet(tasksetData)
  TaskSet.findByIdAndUpdate(tasksetData._id, tasksetData, {upsert:true}, (err, taskset) ->
    callback(err, taskset)
    )

db.close()

module.exports = saveCompletedTaskset

Add reviewable status to submitted hits

Add reviewable status to submitted hits


{CompositeDisposable} = require 'atom'

MinimapBookmarksBinding = null

module.exports =
  active: false

  isActive: -> @active

  activate: (state) ->
    @subscriptions = new CompositeDisposable

  consumeMinimapServiceV1: (@minimap) ->
    @minimap.registerPlugin 'bookmarks', this

  deactivate: ->
    @minimap?.unregisterPlugin 'bookmarks'
    @minimap = null

  activatePlugin: ->
    return if @active

    @active = true

    @minimapsSubscription = @minimap.observeMinimaps (minimap) =>
      MinimapBookmarksBinding ?= require './minimap-bookmarks-binding'
      binding = new MinimapBookmarksBinding(minimap)

      @subscriptions.add subscription = minimap.onDidDestroy =>
        binding.destroy()
        @subscriptions.remove(subscription)
        subscription.dispose()

  deactivatePlugin: ->
    return unless @active

    @active = false
    @minimapsSubscription.dispose()
    @subscriptions.dispose()

{CompositeDisposable} = require 'atom'

MinimapBookmarksBinding = null

module.exports =
  active: false

  isActive: -> @active

  bindings: {}

  activate: (state) ->

  consumeMinimapServiceV1: (@minimap) ->
    @minimap.registerPlugin 'bookmarks', this

  deactivate: ->
    @minimap?.unregisterPlugin 'bookmarks'
    @minimap = null

  activatePlugin: ->
    return if @active

    @subscriptions = new CompositeDisposable
    @active = true

    @minimapsSubscription = @minimap.observeMinimaps (minimap) =>
      MinimapBookmarksBinding ?= require './minimap-bookmarks-binding'
      binding = new MinimapBookmarksBinding(minimap)
      @bindings[minimap.id] = binding

      @subscriptions.add subscription = minimap.onDidDestroy =>
        binding.destroy()
        @subscriptions.remove(subscription)
        subscription.dispose()
        delete @bindings[minimap.id]

  deactivatePlugin: ->
    return unless @active

    binding.destroy() for id,binding of @bindings
    @bindings = {}
    @active = false
    @minimapsSubscription.dispose()
    @subscriptions.dispose()

Implement destroying bindings on deactivation


path = require 'path'
Module = require 'module'

# Expose information of current process.
process.__atom_type = 'renderer'
process.resourcesPath = path.resolve process.argv[1], '..', '..', '..'

# We modified the original process.argv to let node.js load the
# atom-renderer.js, we need to restore it here.
process.argv.splice 1, 1

# Add renderer/api/lib to require's search paths, which contains javascript part
# of Atom's built-in libraries.
globalPaths = Module.globalPaths
globalPaths.push path.join(process.resourcesPath, 'renderer', 'api', 'lib')
# And also common/api/lib.
globalPaths.push path.join(process.resourcesPath, 'common', 'api', 'lib')
# And also app.
globalPaths.push path.join(process.resourcesPath, 'app')

# Expose global variables.
global.require = require
global.module = module

# Set the __filename to the path of html file if it's file:// protocol.
if window.location.protocol is 'file:'
  global.__filename = window.location.pathname
  global.__dirname = path.dirname global.__filename

  # Also search for module under the html file.
  module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname)
else
  global.__filename = __filename
  global.__dirname = __dirname

path   = require 'path'
timers = require 'timers'
Module = require 'module'

# Expose information of current process.
process.__atom_type = 'renderer'
process.resourcesPath = path.resolve process.argv[1], '..', '..', '..'

# We modified the original process.argv to let node.js load the
# atom-renderer.js, we need to restore it here.
process.argv.splice 1, 1

# Add renderer/api/lib to require's search paths, which contains javascript part
# of Atom's built-in libraries.
globalPaths = Module.globalPaths
globalPaths.push path.join(process.resourcesPath, 'renderer', 'api', 'lib')
# And also common/api/lib.
globalPaths.push path.join(process.resourcesPath, 'common', 'api', 'lib')
# And also app.
globalPaths.push path.join(process.resourcesPath, 'app')

# Expose global variables.
global.require = require
global.module = module
global.setImmediate = ->
  process.activateUvLoop()
  timers.setImmediate.apply this, arguments
global.clearImmediate = timers.clearImmediate

# Set the __filename to the path of html file if it's file:// protocol.
if window.location.protocol is 'file:'
  global.__filename = window.location.pathname
  global.__dirname = path.dirname global.__filename

  # Also search for module under the html file.
  module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname)
else
  global.__filename = __filename
  global.__dirname = __dirname

Add setImmediate function in renderer.

Add setImmediate function in renderer.


kd              = require 'kd'
React           = require 'kd-react'
immutable       = require 'immutable'
ActivityFlux    = require 'activity/flux'
ChatPane        = require 'activity/components/chatpane'

module.exports = class PublicChatPane extends React.Component

  @defaultProps =
    thread   : immutable.Map()
    messages : immutable.List()
    padded   : no

  channel: (key) -> @props.thread?.getIn ['channel', key]

  onSubmit: ({ value }) ->

    return  unless body = value
    name = @channel 'name'

    unless body.match ///\##{name}///
      body += " ##{name} "

    ActivityFlux.actions.message.createMessage @channel('id'), body

    @moveScrollToBottom()

  moveScrollToBottom: ->

    ChatPaneWrapper = @refs.ChatPaneWrapper.getDOMNode()
    InfiniteScroll = ChatPaneWrapper.querySelector '.InfiniteScroll'
    InfiniteScroll.scrollTop = InfiniteScroll.scrollHeight

  onScrollThresholdReached: ->

    from = @props.messages.first().get('createdAt')
    ActivityFlux.actions.message.loadMessages @channel('id'), from

  render: ->
    <div ref="ChatPaneWrapper">
      <ChatPane
        thread={@props.thread}
        className="PublicChatPane"
        messages={@props.messages}
        onSubmit={@bound 'onSubmit'}
        onScrollThresholdReached={@bound 'onScrollThresholdReached'}
      />
    </div>

kd              = require 'kd'
React           = require 'kd-react'
immutable       = require 'immutable'
ActivityFlux    = require 'activity/flux'
ChatPane        = require 'activity/components/chatpane'

module.exports = class PublicChatPane extends React.Component

  @defaultProps =
    thread   : immutable.Map()
    messages : immutable.List()
    padded   : no

  channel: (key) -> @props.thread?.getIn ['channel', key]

  onSubmit: ({ value }) ->

    return  unless body = value
    name = @channel 'name'

    unless body.match ///\##{name}///
      body += " ##{name} "

    ActivityFlux.actions.message.createMessage @channel('id'), body

    @moveScrollToBottom()

  moveScrollToBottom: ->

    ChatPaneWrapper = @refs.ChatPaneWrapper.getDOMNode()
    InfiniteScroll = ChatPaneWrapper.querySelector '.InfiniteScroll'
    InfiniteScroll.scrollTop = InfiniteScroll.scrollHeight

  onLoadMore: ->

    return  unless @props.messages.size
    return  if @props.thread.getIn ['flags', 'isMessagesLoading']

    from = @props.messages.first().get('createdAt')
    kd.utils.defer => ActivityFlux.actions.message.loadMessages @channel('id'), { from }

  render: ->
    <ChatPane
      thread={@props.thread}
      className="PublicChatPane"
      messages={@props.messages}
      onSubmit={@bound 'onSubmit'}
      onLoadMore={@bound 'onLoadMore'}
      isParticipant={@channel 'isParticipant'}
    />

activity: Switch to Scroller api in PublicChatPane

Conflicts:
	client/activity/lib/components/publicchatpane/index.coffee


$ ->
  $('.flash').delay(4000).fadeOut()
  $('#close_flash_early').click( -> closeFlashEarly())

closeFlashEarly = ->
  $('.flash').stop().fadeOut()

$ ->
  # Close flashes.
  $('.flash').delay(4000).fadeOut()
  $('#close_flash_early').click( -> closeFlashEarly())

  # Nothing to see here, move along.
  keys = []
  konami = '38,38,40,40,37,39,37,39,66,65'
  $(document).keydown (event) =>
    keys.push(event.keyCode)
    if (keys.toString().indexOf(konami) >= 0)
      alert('You have got to be kidding us.')
      alert('The konami code? Really?')
      alert("Ok. Fine. You've earned it.")
      document.location = 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
      keys = []

# Called when a user clicks a flash's [x].
closeFlashEarly = ->
  $('.flash').stop().fadeOut()

Fix an edge case where the user types in some weird combination of keys.


_ = require 'underscore'
Q = require 'bluebird-q'
Backbone = require 'backbone'
FilterArtworks = require '../../../../collections/filter_artworks.coffee'
initCarousel = require '../../../../components/merry_go_round/index.coffee'
template = -> require('./index.jade') arguments...

module.exports = class HeroArtworksCarousel extends Backbone.View

  defaults:
    filterOptions:
      sort: '-partner_updated_at'
      for_sale: true
      size: 10

  initialize: (options = {}) ->
    { @partner, @filterOptions } = _.defaults options, @defaults

  startUp: ->
    @fetchArtworks().then(@initCarousel).done()

  fetchArtworks: ->
    artworks = new FilterArtworks []
    data = _.extend {}, @filterOptions, partner_id: @partner.get('id')
    Q(artworks.fetch data: data).then -> artworks

  initCarousel: (artworks) =>
    return @remove() unless artworks.length >= 5

    @$el.html template artworks: artworks
    initCarousel @$el, imagesLoaded: true

_ = require 'underscore'
Q = require 'bluebird-q'
Backbone = require 'backbone'
FilterArtworks = require '../../../../collections/filter_artworks.coffee'
initCarousel = require '../../../../components/merry_go_round/index.coffee'
template = -> require('./index.jade') arguments...

module.exports = class HeroArtworksCarousel extends Backbone.View

  defaults:
    filterOptions:
      sort: '-partner_updated_at'
      for_sale: true
      size: 10

  initialize: (options = {}) ->
    { @partner, @filterOptions } = _.defaults options, @defaults

  startUp: ->
    @fetchArtworks().then(@initCarousel).done()

  fetchArtworks: ->
    artworks = new FilterArtworks []
    data = _.extend {}, @filterOptions, partner_id: @partner.get('id')
    Q(artworks.fetch data: data).then -> artworks

  initCarousel: (artworks) =>
    return @remove() unless artworks.length >= 5

    @$el.html template artworks: artworks
    initCarousel @$el, wrapAround: true, imagesLoaded: true, (carousel) ->
      carousel.cells.flickity.resize()

Fix carousel initial rendering issue.

Fix carousel initial rendering issue.


((E, $) ->
  'use strict'

  $(document).ready ->
    $('#lettersVisibility').on 'click', (e) ->
      E.accounts.changeUnmarkVisibility()

    $('#labelLettersVisibility').on 'click', (e) ->
      if $('#lettersVisibility').is(':checked')
        $('#lettersVisibility').prop('checked', false)
      else
        $('#lettersVisibility').prop('checked', true)

      E.accounts.changeUnmarkVisibility()

  E.accounts =
    changeUnmarkVisibility: ->
      unmarkLines = $('.active-list .unmark').closest('tr')

      if unmarkLines.is(':visible')
        unmarkLines.hide()
      else
        unmarkLines.show()

) ekylibre, jQuery

((E, $) ->
  'use strict'

  $(document).ready ->
    E.accounts.changeUnmarkVisibility()

    $('#lettersVisibility').on 'click', (e) ->
      E.accounts.changeUnmarkVisibility()

    $('#labelLettersVisibility').on 'click', (e) ->
      if $('#lettersVisibility').is(':checked')
        $('#lettersVisibility').prop('checked', false)
      else
        $('#lettersVisibility').prop('checked', true)

      E.accounts.changeUnmarkVisibility()

  E.accounts =
    changeUnmarkVisibility: ->
      unmarkLines = $('.active-list .unmark').closest('tr')

      if unmarkLines.is(':visible')
        unmarkLines.hide()
      else
        unmarkLines.show()

) ekylibre, jQuery

Add mask literate elements by default

Add mask literate elements by default


Darkswarm.directive "stripeElements", ($injector, StripeElements) ->
  restrict: 'E'
  template: "\

\

\
             "

  link: (scope, elem, attr)->
    if $injector.has('stripeObject')
      stripe = $injector.get('stripeObject')

      card = stripe.elements().create 'card',
        hidePostalCode: false
        style:
          base:
            fontFamily: "Roboto, Arial, sans-serif"
            fontSize: '16px'
            color: '#5c5c5c'
            '::placeholder':
              color: '#6c6c6c'
      card.mount('#card-element')

      # Elements validates user input as it is typed. To help your customers
      # catch mistakes, you should listen to change events on the card Element
      # and display any errors:
      card.addEventListener 'change', (event) ->
        displayError = document.getElementById('card-errors')
        if event.error
          displayError.textContent = event.error.message
        else
          displayError.textContent = ''
        return

      StripeElements.stripe = stripe
      StripeElements.card = card

Darkswarm.directive "stripeElements", ($injector, StripeElements) ->
  restrict: 'E'
  template: "\

\

\
             "

  link: (scope, elem, attr)->
    if $injector.has('stripeObject')
      stripe = $injector.get('stripeObject')

      card = stripe.elements().create 'card',
        hidePostalCode: true
        style:
          base:
            fontFamily: "Roboto, Arial, sans-serif"
            fontSize: '16px'
            color: '#5c5c5c'
            '::placeholder':
              color: '#6c6c6c'
      card.mount('#card-element')

      # Elements validates user input as it is typed. To help your customers
      # catch mistakes, you should listen to change events on the card Element
      # and display any errors:
      card.addEventListener 'change', (event) ->
        displayError = document.getElementById('card-errors')
        if event.error
          displayError.textContent = event.error.message
        else
          displayError.textContent = ''
        return

      StripeElements.stripe = stripe
      StripeElements.card = card

Hide postcode - not necessary as passed from billing address and wrecks mobile UX


describe 'Graph Editor', ->
  win = null
  doc = null
  before ->
    iframe = document.getElementById 'app'
    win = iframe.contentWindow
    doc = iframe.contentDocument

  describe 'component search', ->
    search = null
    it 'should initially show the breadcrumb', ->
      search = doc.querySelector 'noflo-search'
      chai.expect(search).to.be.an 'object'
      chai.expect(search.classList.contains('overlay')).to.equal true
    it 'when clicked it should show the search input', (done) ->
      breadcrumb = search.shadowRoot.querySelector '#breadcrumb'
      chai.expect(breadcrumb).to.be.an 'object'
      Syn.click breadcrumb
      setTimeout ->
        chai.expect(search.classList.contains('overlay')).to.equal false
        done()
      , 500
    it 'should initially show results', (done) ->
      setTimeout ->
        chai.expect(search.results.length).to.be.above 10
        done()
      , 1800
    it 'should narrow them down when something is written', (done) ->
      input = search.shadowRoot.querySelector '#search'
      Syn.click(input)
      .type 'GetEle'
      setTimeout ->
        chai.expect(search.results.length).to.equal 1
        done()
      , 1000

describe 'Graph Editor', ->
  win = null
  doc = null
  before ->
    iframe = document.getElementById 'app'
    win = iframe.contentWindow
    doc = iframe.contentDocument

  describe 'runtime', ->
    runtime = null
    it 'should be available as an element', ->
      runtime = doc.querySelector 'noflo-runtime'
    it 'should have the IFRAME runtime selected', ->
      chai.expect(runtime.runtime).to.be.an 'object'
    it 'should connect automatically to the IFRAME provider', (done) ->
      @timeout 20000
      if runtime.online
        chai.expect(runtime.online).to.equal true
        done()
        return

      runtime.runtime.on 'status', (status) ->
        return unless status.online
        chai.expect(runtime.online).to.equal true
        done()

  describe 'component search', ->
    search = null
    it 'should initially show the breadcrumb', ->
      search = doc.querySelector 'noflo-search'
      chai.expect(search).to.be.an 'object'
      chai.expect(search.classList.contains('overlay')).to.equal true
    it 'when clicked it should show the search input', (done) ->
      breadcrumb = search.shadowRoot.querySelector '#breadcrumb'
      chai.expect(breadcrumb).to.be.an 'object'
      Syn.click breadcrumb
      setTimeout ->
        chai.expect(search.classList.contains('overlay')).to.equal false
        done()
      , 500
    it 'should initially show results', (done) ->
      setTimeout ->
        chai.expect(search.results.length).to.be.above 10
        done()
      , 1000
    it 'should narrow them down when something is written', (done) ->
      input = search.shadowRoot.querySelector '#search'
      Syn.click(input)
      .type 'GetEle'
      setTimeout ->
        chai.expect(search.results.length).to.equal 1
        done()
      , 1000

Check runtime status as part of the tests


Task = require 'task'

describe "Task", ->
  describe "populating the window with fake properties", ->
    describe "when jQuery is loaded in a child process", ->
      it "doesn't log to the console", ->
        spyOn(console, 'log')
        spyOn(console, 'error')
        spyOn(console, 'warn')

        jqueryTask = new Task('fixtures/jquery-task-handler.coffee')
        jqueryLoaded = false
        jqueryTask.start (loaded) -> jqueryLoaded = loaded

        waitsFor "child process to start and jquery to be required", 5000, ->
          jqueryLoaded

        runs ->
          expect(jqueryLoaded).toBeTruthy()
          expect(console.log).not.toHaveBeenCalled()
          expect(console.error).not.toHaveBeenCalled()
          expect(console.warn).not.toHaveBeenCalled()

Task = require 'task'

describe "Task", ->
  describe "populating the window with fake properties", ->
    describe "when jQuery is loaded in a child process", ->
      it "doesn't log to the console", ->
        spyOn(console, 'log')
        spyOn(console, 'error')
        spyOn(console, 'warn')

        jqueryTask = new Task('fixtures/jquery-task-handler')
        jqueryLoaded = false
        jqueryTask.start (loaded) -> jqueryLoaded = loaded

        waitsFor "child process to start and jquery to be required", 5000, ->
          jqueryLoaded

        runs ->
          expect(jqueryLoaded).toBeTruthy()
          expect(console.log).not.toHaveBeenCalled()
          expect(console.error).not.toHaveBeenCalled()
          expect(console.warn).not.toHaveBeenCalled()

Drop suffix from handler path

Drop suffix from handler path


ETahi.AuthorViewComponent = Ember.Component.extend DragNDrop.Dragable,
  classNames: ['authors-overlay-item']
  classNameBindings: ['hoverState:__hover', 'isEditable:__editable']

  editState: false
  hoverState: false
  deleteState: false

  attachHoverEvent: (->
    self = this
    toggleHoverClass = (e) ->
      self.toggleProperty 'hoverState'

    @$().hover(toggleHoverClass, toggleHoverClass)
  ).on('didInsertElement')

  teardownHoverEvent: (->
    @$().off('mouseenter mouseleave');
  ).on('willDestroyElement')

  dragStart: (e) ->
    e.dataTransfer.effectAllowed = 'move'
    ETahi.set('dragItem', @get('author'))

  dragEnd: (e) ->
    DragNDrop.draggingStopped('.author-drop-target')

  actions:
    delete: ->
      @sendAction 'delete', @get('author')

    save: ->
      @sendAction 'save', @get('author')
      @set 'editState', false

    toggleEditForm: ->
      @toggleProperty 'editState'

    toggleDeleteConfirmation: ->
      @toggleProperty 'deleteState'

ETahi.AuthorViewComponent = Ember.Component.extend DragNDrop.Dragable,
  classNames: ['authors-overlay-item']
  classNameBindings: ['hoverState:__hover', 'isEditable:__editable']

  editState: false
  hoverState: false
  deleteState: false

  attachHoverEvent: (->
    toggleHoverClass = (e) =>
      @toggleProperty 'hoverState'

    @$().hover toggleHoverClass, toggleHoverClass
  ).on('didInsertElement')

  teardownHoverEvent: (->
    @$().off('mouseenter mouseleave');
  ).on('willDestroyElement')

  dragStart: (e) ->
    e.dataTransfer.effectAllowed = 'move'
    ETahi.set('dragItem', @get('author'))

  dragEnd: (e) ->
    DragNDrop.draggingStopped('.author-drop-target')

  actions:
    delete: ->
      @sendAction 'delete', @get('author')

    save: ->
      @sendAction 'save', @get('author')
      @set 'editState', false

    toggleEditForm: ->
      @toggleProperty 'editState'

    toggleDeleteConfirmation: ->
      @toggleProperty 'deleteState'

Fix Author View hover class toggle

Fix Author View hover class toggle


class CMS.Views.ModuleEdit extends Backbone.View
  tagName: 'section'
  className: 'edit-pane'

  events:
    'click .cancel': 'cancel'
    'click .module-edit': 'editSubmodule'
    'click .save-update': 'save'

  initialize: ->
    @$el.load @model.editUrl(), =>
      @model.loadModule(@el)
      @$el.find('.preview').each (idx, previewEl) =>
          @model.loadPreview(previewEl)

  save: (event) ->
    event.preventDefault()
    @model.save().success(->
      alert("Your changes have been saved.")
    ).error(->
      alert("There was an error saving your changes. Please try again.")
    )

  cancel: (event) ->
    event.preventDefault()
    CMS.popView()

  editSubmodule: (event) ->
    event.preventDefault()
    CMS.pushView(new CMS.Views.ModuleEdit(model: new CMS.Models.Module(id: $(event.target).data('id'), type: $(event.target).data('type'))))

class CMS.Views.ModuleEdit extends Backbone.View
  tagName: 'section'
  className: 'edit-pane'

  events:
    'click .cancel': 'cancel'
    'click .module-edit': 'editSubmodule'
    'click .save-update': 'save'

  initialize: ->
    @$el.load @model.editUrl(), =>
      @model.loadModule(@el)
      @$el.find('.preview :first-child').each (idx, previewEl) =>
          @model.loadPreview(previewEl)

  save: (event) ->
    event.preventDefault()
    @model.save().success(->
      alert("Your changes have been saved.")
    ).error(->
      alert("There was an error saving your changes. Please try again.")
    )

  cancel: (event) ->
    event.preventDefault()
    CMS.popView()

  editSubmodule: (event) ->
    event.preventDefault()
    CMS.pushView(new CMS.Views.ModuleEdit(model: new CMS.Models.Module(id: $(event.target).data('id'), type: $(event.target).data('type'))))

Load preview modules with actual preview elements


exports.loadUsers = (params, callback) ->
  if @isId params
    params = teamId: params
  else unless params and typeof params is 'object'
    throw new TSArgsError 'teamsnap.loadUsers', 'must provide a teamId or
      query parameters'

  @loadItems 'user', params, callback

exports.loadMe = (callback) ->
  @collections.root.queryItem('me', callback)

exports.saveUser = (user, callback) ->
  unless user
    throw new TSArgsError 'teamsnap.saveUser', "`user` must be provided"
  unless @isItem user, 'user'
    throw new TSArgsError 'teamsnap.saveUser', "`user.type` must be
      'user'"
  unless user.email?.trim()
    return @reject 'You must provide an email for the user.', 'email', callback

  @saveItem user, callback

exports.sendEmailValidation = (callback) ->
  @collections.users.exec('sendEmailValidation', userId).pop().callback callback

exports.loadUsers = (params, callback) ->
  if @isId params
    params = teamId: params
  else unless params and typeof params is 'object'
    throw new TSArgsError 'teamsnap.loadUsers', 'must provide a teamId or
      query parameters'

  @loadItems 'user', params, callback

exports.loadMe = (callback) ->
  @collections.root.queryItem('me', callback)

exports.saveUser = (user, callback) ->
  unless user
    throw new TSArgsError 'teamsnap.saveUser', "`user` must be provided"
  unless @isItem user, 'user'
    throw new TSArgsError 'teamsnap.saveUser', "`user.type` must be
      'user'"
  unless user.email?.trim()
    return @reject 'You must provide an email for the user.', 'email', callback

  @saveItem user, callback

exports.sendEmailValidation = (callback) ->
  @collections.users.exec('sendEmailValidation').pop().callback callback

Remove unused param for sendEmailValidation


_ = require 'underscore'
{ API_URL } = require('sharify').data
Backbone = require 'backbone'
RelatedLinksView = require '../view.coffee'
Partners = require '../../../collections/partners.coffee'

module.exports = class RelatedRepresentationsLinksView extends RelatedLinksView
  headerTemplate: _.template '
Gallery Representation
'
  wrapperTemplate: _.template '
<%= links %>
'

  initialize: (options = {}) ->
    @collection = new Partners
    @collection.url = "#{API_URL}/api/v1/artist/#{options.id}/partners"
    @collection.fetch data: size: 20, represented_by: true
    super

_ = require 'underscore'
{ API_URL } = require('sharify').data
Backbone = require 'backbone'
RelatedLinksView = require '../view.coffee'
Partners = require '../../../collections/partners.coffee'
CurrentUser = require '../../../models/current_user.coffee'

module.exports = class RelatedRepresentationsLinksView extends RelatedLinksView
  headerTemplate: _.template '
Gallery Representation
'
  wrapperTemplate: _.template '
<%= links %>
'

  initialize: (options = {}) ->
    return unless CurrentUser.orNull()?.isAdmin()
    @collection = new Partners
    @collection.url = "#{API_URL}/api/v1/artist/#{options.id}/partners"
    @collection.fetch data: size: 20, represented_by: true
    super

Disable artist representation links for everyone but admins for the time being


`import Em from "vendor/ember"`

Component = Em.Component.extend
    options: Em.computed "content.{options,prompt,is_optional}", ->
        prompt = @get "content.prompt"
        prompt = "" if not prompt and @get "content.is_optional"
        options = @get "content.options"

        if typeof prompt is "string" and prompt isnt options[0]?.label
            [ label: prompt, value: "" ].concat options
        else
            options

    isStatic: Em.computed "content.{options,editable,is_optional}", ->
        options = @get "content.options"
        isEditable = @get "editable"
        isOptional = @get "is_optional"

        if options.length is 1 and not isEditable and not isOptional
            @set "content.value", options[0].value
            true
        else
            false

    isCustom: Em.computed "options", "content.value", ->
        values = (o.value for o in @get "options")
        value = @get "content.value"
        value = "" if value is null
        value not in values

    customize: Em.computed "isCustom", -> @get "isCustom"

    actions:
        customize: ->
            @toggleProperty "customize"
            if not @get "customize"
                if @get "isCustom"
                    @set "content.value", @get("options")[0].value
            Em.run.next => @$("input,select")[0].focus()

`export default Component`

`import Em from "vendor/ember"`

Component = Em.Component.extend
    sanitizeValue: Em.on "init", ->
        value = @get "content.value"
        if typeof value isnt "string"
            options = @get "options"
            @set "content.value", options[0]?.value or ""

    options: Em.computed "content.{options,prompt,is_optional}", ->
        prompt = @get "content.prompt"
        prompt = "" if not prompt and @get "content.is_optional"
        options = @get "content.options"

        if typeof prompt is "string" and prompt isnt options[0]?.label
            [ label: prompt, value: "" ].concat options
        else
            options

    isStatic: Em.computed "content.{options,editable,is_optional}", ->
        options = @get "content.options"
        isEditable = @get "editable"
        isOptional = @get "is_optional"

        if options.length is 1 and not isEditable and not isOptional
            @set "content.value", options[0].value
            true
        else
            false

    isCustom: Em.computed "options", "content.value", ->
        values = (o.value for o in @get "options")
        value = @get "content.value"
        value not in values

    customize: Em.computed "isCustom", -> @get "isCustom"

    actions:
        customize: ->
            @toggleProperty "customize"
            if not @get "customize"
                if @get "isCustom"
                    @set "content.value", @get("options")[0].value
            Em.run.next => @$("input,select")[0].focus()

`export default Component`

Initialize select value taking prompt into account.


React = require 'react'
{div} = require 'reactionary'
{debounce} = require 'underscore-plus'
SubscriberMixin = require './subscriber-mixin'
CursorComponent = require './cursor-component'

module.exports =
CursorsComponent = React.createClass
  displayName: 'CursorsComponent'
  mixins: [SubscriberMixin]

  cursorBlinkIntervalHandle: null

  render: ->
    {editor} = @props
    blinkOff = @state.blinkCursorsOff

    div className: 'cursors',
      for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
        {cursor} = selection
        CursorComponent({key: cursor.id, cursor, blinkOff})

  getInitialState: ->
    blinkCursorsOff: false

  componentDidMount: ->
    {editor} = @props
    @subscribe editor, 'cursors-moved', @pauseCursorBlinking
    @startBlinkingCursors()

  startBlinkingCursors: ->
    @cursorBlinkIntervalHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2)

  startBlinkingCursorsAfterDelay: null # Created lazily

  stopBlinkingCursors: ->
    clearInterval(@cursorBlinkIntervalHandle)
    @setState(blinkCursorsOff: false)

  toggleCursorBlink: -> @setState(blinkCursorsOff: not @state.blinkCursorsOff)

  pauseCursorBlinking: ->
    @stopBlinkingCursors()
    @startBlinkingCursorsAfterDelay ?= debounce(@startBlinkingCursors, @props.cursorBlinkResumeDelay)
    @startBlinkingCursorsAfterDelay()

React = require 'react'
{div} = require 'reactionary'
{debounce} = require 'underscore-plus'
SubscriberMixin = require './subscriber-mixin'
CursorComponent = require './cursor-component'

module.exports =
CursorsComponent = React.createClass
  displayName: 'CursorsComponent'
  mixins: [SubscriberMixin]

  cursorBlinkIntervalHandle: null

  render: ->
    {editor} = @props
    blinkOff = @state.blinkCursorsOff

    div className: 'cursors',
      for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
        {cursor} = selection
        CursorComponent({key: cursor.id, cursor, blinkOff})

  getInitialState: ->
    blinkCursorsOff: false

  componentDidMount: ->
    {editor} = @props
    @subscribe editor, 'cursors-moved', @pauseCursorBlinking
    @startBlinkingCursors()

  componentWillUnmount: ->
    @stopBlinkingCursors()

  startBlinkingCursors: ->
    @cursorBlinkIntervalHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2)

  startBlinkingCursorsAfterDelay: null # Created lazily

  stopBlinkingCursors: ->
    clearInterval(@cursorBlinkIntervalHandle)
    @setState(blinkCursorsOff: false)

  toggleCursorBlink: -> @setState(blinkCursorsOff: not @state.blinkCursorsOff)

  pauseCursorBlinking: ->
    @stopBlinkingCursors()
    @startBlinkingCursorsAfterDelay ?= debounce(@startBlinkingCursors, @props.cursorBlinkResumeDelay)
    @startBlinkingCursorsAfterDelay()

Clear cursor blink interval when editor component unmounts


{ tr,td,button,i } = React.DOM

shared_filesystem_storage.AccessItem = React.createClass
  getInitialState: ->
    deleting: false

  handleDelete: (e) ->
    e.preventDefault()
    shared_filesystem_storage.ConfirmDialog.ask 'Are you sure?',
      #validationTerm: @props.shared_network.name
      description: 'Would you like to delete this access?'
      confirmLabel: 'Yes, delete it!'
    .then => @delete()
    .fail -> null

  delete: ->
    @setState deleting: true

    @props.ajax.delete "shares/#{@props.shareId}/rules/#{@props.rule.id}",
      success: () =>
        @props.handleDeleteRule @props.rule
      error: ( jqXHR, textStatus, errorThrown ) =>
        @setState deleting: null

  render: ->
    tr className: ('updating' if @state.deleting),
      td null, @props.rule.access_type
      td null, @props.rule.access_to
      td null, @props.rule.access_level
      td null, @props.rule.state
      td className: 'snug',
        button className: 'btn btn-danger btn-sm', onClick: @handleDelete,
          i className: 'fa fa-minus'

{ tr,td,button,i } = React.DOM

shared_filesystem_storage.AccessItem = React.createClass
  getInitialState: ->
    deleting: false

  handleDelete: (e) ->
    e.preventDefault()
    shared_filesystem_storage.ConfirmDialog.ask 'Are you sure?',
      #validationTerm: @props.shared_network.name
      description: 'Would you like to delete this access?'
      confirmLabel: 'Yes, delete it!'
    .then => @delete()
    .fail -> null

  delete: ->
    @setState deleting: true

    @props.ajax.delete "shares/#{@props.shareId}/rules/#{@props.rule.id}",
      success: () =>
        @props.handleDeleteRule @props.rule
      error: ( jqXHR, textStatus, errorThrown ) =>
        @setState deleting: null

  humanizeAccessLevel: () ->
    switch @props.rule.access_level
      when 'ro' then 'read only'
      when 'rw' then 'read/write'
      else @props.rule.access_level

  render: ->
    tr className: ('updating' if @state.deleting),
      td null, @props.rule.access_type
      td null, @props.rule.access_to
      td className: "#{if @props.rule.access_level == 'rw' then 'text-success' else 'text-info'}",
        i className: "fa fa-fw fa-#{if @props.rule.access_level == 'rw' then 'pencil-square' else 'eye'}"
        @humanizeAccessLevel()
      td null, @props.rule.state
      td className: 'snug',
        button className: 'btn btn-danger btn-sm', onClick: @handleDelete,
          i className: 'fa fa-minus'

Add icons and styling to access level, humanize access level


###
  jquery.turbolinks.js ~ v1.0.0-rc1 ~ https://github.com/kossnocorp/jquery.turbolinks

  jQuery plugin for drop-in fix binded events problem caused by Turbolinks

  The MIT License

  Copyright (c) 2012 Sasha Koss
###

$ = require?('jquery') || window.jQuery

# List for store callbacks passed to `$` or `$.ready`
callbacks = []

# Call each callback in list
ready = ->
  callback($) for callback in callbacks

# Turbolinks ready event
turbolinksReady = ->
  $.isReady = true
  ready()

# Fetch event handler
fetch = ->
  $(document).off(undefined, '**')
  $.isReady = false

# Bind `ready` to DOM ready event
$(ready)

# Store callbacks in list on `$` and `$.ready`
$.fn.ready = (callback) ->
  callbacks.push(callback)
  callback($) if $.isReady

# Bind ready to passed event
$.setReadyEvent = (event) ->
  $(document)
    .off('.turbolinks-ready')
    .on(event + '.turbolinks-ready', turbolinksReady)

# Bind fetch event
$.setFetchEvent = (event) ->
  $(document)
    .off('.turbolinks-fetch')
    .on(event + '.turbolinks-fetch', fetch)

# Bind `ready` to Tubolinks page:load
$.setReadyEvent('page:load')

# Bind fetch to Turbolinks page:fetch
$.setFetchEvent('page:fetch')

###
  jquery.turbolinks.js ~ v1.0.0-rc1 ~ https://github.com/kossnocorp/jquery.turbolinks

  jQuery plugin for drop-in fix binded events problem caused by Turbolinks

  The MIT License

  Copyright (c) 2012 Sasha Koss
###

$ = window.jQuery or require?('jquery')

# List for store callbacks passed to `$` or `$.ready`
callbacks = []

# Call each callback in list
ready = ->
  callback($) for callback in callbacks

# Turbolinks ready event
turbolinksReady = ->
  $.isReady = true
  ready()

# Fetch event handler
fetch = ->
  $(document).off(undefined, '**')
  $.isReady = false

# Bind `ready` to DOM ready event
$(ready)

# Store callbacks in list on `$` and `$.ready`
$.fn.ready = (callback) ->
  callbacks.push(callback)
  callback($) if $.isReady

# Bind ready to passed event
$.setReadyEvent = (event) ->
  $(document)
    .off('.turbolinks-ready')
    .on(event + '.turbolinks-ready', turbolinksReady)

# Bind fetch event
$.setFetchEvent = (event) ->
  $(document)
    .off('.turbolinks-fetch')
    .on(event + '.turbolinks-fetch', fetch)

# Bind `ready` to Tubolinks page:load
$.setReadyEvent('page:load')

# Bind fetch to Turbolinks page:fetch
$.setFetchEvent('page:fetch')

Load jQuery from global namespace first.

This fixes the cases wherein you may be using an AMD system, but load jQuery outside AMD.


$(document).ready ->

  assert = (condition) -> throw 'Assertion failed' unless condition

  contents = """

  #################################################################
  #################################################################
  #################################################################
  #################################################################

  """

  # assert contents == $('#sample').text()

  new window.TextFadeIn( document.getElementById('demo1'),           'milliseconds': 1  ).run()
  new window.TextFadeIn( document.getElementById('demo2'), contents, 'milliseconds': 1  ).run()
  new window.TextFadeIn( document.getElementById('demo3'), contents, 'milliseconds': 10 ).run()
  new window.TextFadeIn( document.getElementById('demo4'), contents, 'threads':      10 ).run()

  # assert contents == $('#sample').text()

$(document).ready ->

  assert = (condition) -> throw 'Assertion failed' unless condition

  contents = """

  #################################################################
  #################################################################
  #################################################################
  #################################################################

  """

  # assert contents == $('#sample').text()

  new window.TextFadeIn( document.getElementById('demo1'),           'milliseconds': 1  ).run()
  new window.TextFadeIn( document.getElementById('demo2'), contents, 'milliseconds': 1  ).run()
  new window.TextFadeIn( document.getElementById('demo3'), contents, 'milliseconds': 10 ).run()
  new window.TextFadeIn( document.getElementById('demo4'), contents, 'threads':      3  ).run()

  # assert contents == $('#sample').text()

Decrease threads number in order to show threads working

Conflicts:
	text-fade-in-demo.js.coffee


class Water.TreeFetcher extends Backbone.Model
  fetch: (node_type, path) =>
    @trigger "start_fetch"
    url = [@attributes.repository_path, node_type, @attributes.ref , path].join("/")
    $.ajax
      url: url
      data: bare: 1
      success: (data) => @data_did_fetch(data)
      dataType: "html"
  data_did_fetch: (data) =>
    @set(data: data)

class Water.TreeFetcher extends Backbone.Model
  fetch: (node_type, path) =>
    @trigger "start_fetch"
    url = [@attributes.repository_path, node_type, @attributes.ref , path].join("/")
    $.ajax
      url: url
      data: bare: 1
      success: (data) => @data_did_fetch(data)
      error: (hdr, status, error) => @error(hdr, status, error)
      dataType: "html"
  data_did_fetch: (data) =>
    @set(data: data)
  error: (jqXHR, status, error) =>
    @set(data: status)

Add rudimentary error handling for tree ftecher


# Keybindings require three things to be fully defined: A selector that is
# matched against the focused element, the keystroke and the command to
# execute.
#
# Below is a basic keybinding which registers on all platforms by applying to
# the root workspace element.

# For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps
'.workspace':
  'ctrl-alt-t': 'task-list:toggle'

# Keybindings require three things to be fully defined: A selector that is
# matched against the focused element, the keystroke and the command to
# execute.
#
# Below is a basic keybinding which registers on all platforms by applying to
# the root workspace element.

# For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps
'atom-workspace':
  'ctrl-alt-t': 'task-list:toggle'

Fix for depreciated class warning

Fix for depreciated class warning


window.App ||= {}
class App.Base

  constructor: ->
    if (window.jQuery) then @setClearEventHandlers() # clearing application event handlers only possible with jQuery
    return this

  ###
  Run the new action for the create action.  Generally the create action will 'render :new' if there was a problem.
  This prevents doubling the code for each action.
  ###
  create: ->
    if typeof $this.new == 'function'
      return $this.new()

  ###
  Run the edit action for the update action.  Generally the update action will 'render :edit' if there was a problem.
  This prevents doubling the code for each action.
  ###
  update: ->
    if typeof $this.edit == 'function'
      return $this.edit()

  ###
  Clear event handlers with a blank namespace.  This will prevent window and document event handlers from stacking
  when each new page is fetched.  Adding a namespace to your events will prevent them from being removed between page
  loads, i.e. "$(window).on 'scroll.app', myHandler"
  ###
  clearEventHandlers: ->
    jQuery(document).on 'page:before-change', ->
      for element in [window, document]
        for event, handlers in jQuery._data(element, 'events')
          for handler in handlers
            if handler.namespace == '' then $(element).off event, handler.handler

window.App ||= {}
class App.Base

  constructor: ->
    if (window.jQuery) then @setClearEventHandlers() # clearing application event handlers only possible with jQuery
    return this

  ###
  Run the new action for the create action.  Generally the create action will 'render :new' if there was a problem.
  This prevents doubling the code for each action.
  ###
  create: ->
    if typeof $this.new == 'function'
      return $this.new()

  ###
  Run the edit action for the update action.  Generally the update action will 'render :edit' if there was a problem.
  This prevents doubling the code for each action.
  ###
  update: ->
    if typeof $this.edit == 'function'
      return $this.edit()

  ###
  Clear event handlers with a blank namespace.  This will prevent window and document event handlers from stacking
  when each new page is fetched.  Adding a namespace to your events will prevent them from being removed between page
  loads, i.e. "$(window).on 'scroll.app', myHandler"
  ###
  setClearEventHandlers: ->
    jQuery(document).on 'page:before-change', ->
      for element in [window, document]
        for event, handlers in jQuery._data(element, 'events')
          for handler in handlers
            if handler.namespace == '' then $(element).off event, handler.handler

Make sure the jQuery event handler is called.


mongoose = require 'mongoose'
plugins = require '../plugins/plugins'

AnalyticsLogEventSchema = new mongoose.Schema({
  created:
    type: Date
    'default': Date.now
}, {strict: false})
AnalyticsLogEventSchema.index({event: 1, created: -1})

module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)

mongoose = require 'mongoose'
plugins = require '../plugins/plugins'

AnalyticsLogEventSchema = new mongoose.Schema({
  created:
    type: Date
    'default': Date.now
}, {strict: false})
AnalyticsLogEventSchema.index event: 1
AnalyticsLogEventSchema.index created: -1

module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)

Update analytics.log.event indexes to match prod

These were applied as single indexes directly in production.


#= require test_helper

testPhases = undefined
testTemplate = undefined

module 'Unit: ManuscriptManagerTemplate',
  setup: ->
    phase = name: 'First Phase', task_types: ['ATask', 'AnotherTask']
    @testThing = 5
    template =
      name: 'A name'
      paper_type: 'A type'
      template:
        phases: [phase]

    Ember.run =>
      testTemplate = ETahi.ManuscriptManagerTemplate.create(template)
      testPhases = testTemplate.get('phases')

test '#init creates phase objects for the phases in a template', ->
  equal testPhases.length, 1
  equal testPhases.get('firstObject.name'), 'First Phase'

test '#init creates task objects for the task types in a phase', ->
  tasks = testPhases.get('firstObject.tasks')
  equal tasks.get('length'), 2
  equal tasks.get('firstObject.title'), 'ATask'

test "#init sets the paperType", ->
  equal testTemplate.get('paperType'), 'A type'

test "#init sets the template property to null", ->
  equal testTemplate.get('template'), null

#= require test_helper

testPhases = undefined
testTemplate = undefined

module 'Unit: ManuscriptManagerTemplate',
  setup: ->
    phase = name: 'First Phase', task_types: ['ATask', 'AnotherTask']
    @testThing = 5
    template =
      name: 'A name'
      id: 1
      paper_type: 'A type'
      template:
        phases: [phase]

    Ember.run =>
      testTemplate = ETahi.ManuscriptManagerTemplate.create(template)
      testPhases = testTemplate.get('phases')

test '#init creates phase objects for the phases in a template', ->
  equal testPhases.length, 1
  equal testPhases.get('firstObject.name'), 'First Phase'

test '#init creates task objects for the task types in a phase', ->
  tasks = testPhases.get('firstObject.tasks')
  equal tasks.get('length'), 2
  equal tasks.get('firstObject.title'), 'ATask'

test "#init sets the paperType", ->
  equal testTemplate.get('paperType'), 'A type'

test "#init sets the template property to null", ->
  equal testTemplate.get('template'), null

test "#templateJSON serializes the template's state.", ->
  testData = {
    id: 1,
    name: 'A name',
    paper_type: 'A type'
    template: phases: [ {name: 'First Phase', task_types: ['ATask', 'AnotherTask']}]
  }
  deepEqual testTemplate.get('templateJSON'), testData

Add tests for templateJSON property.

Add tests for templateJSON property.


Neighborly.Map = (mapCanvasSelector) ->
  window.mapCanvasSelector = mapCanvasSelector

  $.getScript('https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=Neighborly.MapInitialize')

  Neighborly.MapInitialize = ->
    mapCanvasSelector = '.map-canvas' if typeof mapCanvasSelector is 'undefined'
    mapCanvas = $(mapCanvasSelector)[0]
    mapOptions =
      zoom: 11
      center: new google.maps.LatLng($('.address-coordinates').data('latitude'), $('.address-coordinates').data('longitude'))
      disableDefaultUI: true
    map = new google.maps.Map(mapCanvas, mapOptions)

Neighborly.Map = (mapCanvasSelector) ->
  window.mapCanvasSelector = mapCanvasSelector

  $.getScript('https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=Neighborly.MapInitialize')

  Neighborly.MapInitialize = ->
    mapCanvasSelector = '.map-canvas' if typeof mapCanvasSelector is 'undefined'
    mapCanvas = $(mapCanvasSelector)[0]
    mapOptions =
      zoom: 14
      center: new google.maps.LatLng($('.address-coordinates').data('latitude'), $('.address-coordinates').data('longitude'))
      disableDefaultUI: true
      mapTypeId: 'terrain'
    map = new google.maps.Map(mapCanvas, mapOptions)

Use terrain map type on JS map module

Use terrain map type on JS map module


#= require jquery
#= require jquery_ujs
#= require turbolinks
#= require_tree .

onReady = ->
  $('#gems-search').keyup ->
    if $("#query").val().length
      $.get $(this).attr('action'), $(this).serialize(), null, 'script'
      false

$(document).ready onReady

$(document).on 'page:change', ->
  if window._gaq?
    _gaq.push ['_trackPageview']
  else if window.pageTracker?
    pageTracker._trackPageview()

  onReady()

#= require jquery
#= require jquery_ujs
#= require turbolinks
#= require_tree .

onReady = ->
  $('#gems-search').keyup ->
    $.get $(this).attr('action'), $(this).serialize(), null, 'script'
    false

$(document).ready onReady

$(document).on 'page:change', ->
  if window._gaq?
    _gaq.push ['_trackPageview']
  else if window.pageTracker?
    pageTracker._trackPageview()

  onReady()

Revert "Don't send search query when query field is empty"

This reverts commit 6173b9b468889a31d84460eb95eac5ec9ec767bc.

Necessary because else when someone erases his search query in the input
field, the index page does not show again all the gems.


'use strict'

{toggleGem} = require './actions'
store = require './store'

Gem = React.createClass
  mixins: [Reflux.connect store, 'status']

  onClick: (event) ->
    toggleGem()
    return

  render: ->
    statusStr = if @state?.status then 'activated' else 'deactivated'
    (<p>
      Gem is {statusStr}
      <button onClick={@onClick}>toggle</button>
    </p>)

module.exports = Gem

'use strict'

{toggleGem} = require './actions'
store = require './store'

Gem = React.createClass
  mixins: [Reflux.connect store, 'status']

  onClick: (event) ->
    toggleGem()
    return

  render: ->
    statusStr = if @state?.status then 'activated' else 'deactivated'
    (

Gem is {statusStr}

      <button onClick={@onClick}>toggle</button>

)

module.exports = Gem

Put toggle button on new line

Put toggle button on new line

The moving toggle button is too annoying, let's put it under the updating text.


mongoose = require 'mongoose'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId

mongoose.model "User", new Schema
  user:
    type: String
    required: true
    unique: true
  password:
    type: String
    required: true

User = module.exports.User = mongoose.model "User"

mongoose.model "Chart", new Schema
  owner:
    type: ObjectId
    required: true
    index: true
  title:
    type: String
    required: true

Chart = module.exports.Chart = mongoose.model "Chart"

schema_Point = new Schema
  chart:
    type: ObjectId
    required: true
    index: true
  stamp:
    type: Date
    required: true
  data:
    type: Number
    required: true
schema_Point.index
  chart: 1
  stamp: 1

mongoose.model "Point", schema_Point
Point = module.exports.Point = mongoose.model "Point"

mongoose = require 'mongoose'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId

mongoose.model "User", new Schema
  user:
    type: String
    required: true
    unique: true
  password:
    type: String
    required: true

User = module.exports.User = mongoose.model "User"

mongoose.model "Chart", new Schema
  owner:
    type: String
    required: true
    index: true
  title:
    type: String
    required: true

Chart = module.exports.Chart = mongoose.model "Chart"

schema_Dataset = new Schema
  chart:
    type: ObjectId
    required: true
    index: true
  title:
    type: String
    required: true
  unit:
    type: String
    required: true

mongoose.model "Dataset", schema_Dataset
Dataset = module.exports.Dataset = mongoose.model "Dataset"

schema_Point = new Schema
  dataset:
    type: ObjectId
    required: true
    index: true
  stamp:
    type: Date
    required: true
  data:
    type: Number
    required: true
schema_Point.index
  dataset: 1
  stamp: 1

mongoose.model "Point", schema_Point
Point = module.exports.Point = mongoose.model "Point"

Extend a chart to contain several distinct datasets.


path = require('path')
gulp = require('gulp')
mocha = require('gulp-mocha')
coffeelint = require('gulp-coffeelint')
mochaNotifierReporter = require('mocha-notifier-reporter')

OPTIONS =
	config:
		coffeelint: path.join(__dirname, 'coffeelint.json')
	files:
		coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
		tests: 'lib/**/*.spec.coffee'

gulp.task 'test', ->
	gulp.src(OPTIONS.files.tests, read: false)
		.pipe(mocha({
			reporter: mochaNotifierReporter.decorate('landing')
		}))

gulp.task 'lint', ->
	gulp.src(OPTIONS.files.coffee)
		.pipe(coffeelint({
			optFile: OPTIONS.config.coffeelint
		}))
		.pipe(coffeelint.reporter())

gulp.task 'watch', [ 'test', 'lint' ], ->
	gulp.watch(OPTIONS.files.coffee, [ 'test', 'lint' ])

path = require('path')
gulp = require('gulp')
mocha = require('gulp-mocha')
coffeelint = require('gulp-coffeelint')
mochaNotifierReporter = require('mocha-notifier-reporter')

OPTIONS =
	config:
		coffeelint: path.join(__dirname, 'coffeelint.json')
	files:
		coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
		tests: 'lib/**/*.spec.coffee'
		json: 'lib/**/*.json'

gulp.task 'test', ->
	gulp.src(OPTIONS.files.tests, read: false)
		.pipe(mocha({
			reporter: mochaNotifierReporter.decorate('landing')
		}))

gulp.task 'lint', ->
	gulp.src(OPTIONS.files.coffee)
		.pipe(coffeelint({
			optFile: OPTIONS.config.coffeelint
		}))
		.pipe(coffeelint.reporter())

gulp.task 'watch', [ 'test', 'lint' ], ->
	gulp.watch([ OPTIONS.files.coffee, OPTIONS.files.json ], [ 'test', 'lint' ])

Watch for JSON files when linting

Watch for JSON files when linting


# csso   = require 'csso'
cleanCss = require 'clean-css'
Bundle   = require './bundle'
fs       = require 'fs'
os       = require 'os'
path     = require 'path'

class Css extends Bundle
  constructor: (@options) ->
    @fileExtension = '.css'
    super

  minify: (code) ->
    return code unless @options.minifyCss

    try
      # csso.justDoIt(code, false, false)
      # csso.parse(code, 'stylesheet', true)
      return cleanCss.process(code, {
        keepSpecialComments:0 # remove all
      });
    catch err
      filename = path.resolve(os.tmpDir(), 'tmpcss.css')
      fs.writeFileSync(filename, code)
      console.error("CSSO", err, "temporary file at: ", filename)
      process.exit()

  render: (namespace) ->
    style = ''
    for file in @files
      if file.namespace == namespace
        style += "<link href='#{file.url}' rel='stylesheet' type='text/css'/>"
    return style

  _convertFilename: (filename) ->
    splitted = filename.split('.')
    splitted.splice(0, splitted.length - 1).join('.') + '.css'

module.exports = Css

# csso   = require 'csso'
cleanCss = require 'clean-css'
Bundle   = require './bundle'
fs       = require 'fs'
os       = require 'os'
path     = require 'path'

class Css extends Bundle
  constructor: (@options) ->
    @fileExtension = '.css'
    super

  minify: (code) ->
    return code unless @options.minifyCss

    try
      # csso.justDoIt(code, false, false)
      # csso.parse(code, 'stylesheet', true)
      return cleanCss.process(code, {
        keepSpecialComments:0 # remove all
      });
    catch err
      filename = path.resolve(os.tmpDir(), 'tmpcss.css')
      fs.writeFileSync(filename, code)
      console.error("CSSO", err, "temporary file at: ", filename)
      process.exit()

  render: (namespace) ->
    style = ''
    for file in @files
      if file.namespace == namespace
        url = if typeof file.url == 'boolean' then file.file else file.url
        style += "<link href='#{url}' rel='stylesheet' type='text/css'/>"
    return style

  _convertFilename: (filename) ->
    splitted = filename.split('.')
    splitted.splice(0, splitted.length - 1).join('.') + '.css'

module.exports = Css

Resolve issue with incorrect url for remote CSS when using addCssUrl, and bundle option is false. This results from the file.url property being a boolean value instead of a string, and the result is the link tag is output as <link href='true' /> rather than with the correct url. Looks like this was already fixed for JS files, but not CSS.


'body, .command-palette .editor':
  'meta-p': 'command-palette:toggle'

'.command-palette .editor':
  'meta-p': 'command-palette:toggle'

'body, .command-palette .editor':
  'meta-P': 'command-palette:toggle'

Use meta-P to toggle command palette

Use meta-P to toggle command palette


# base64
#
# @description
# @Copyright 2014 Fantasy <fantasyshao@icloud.com>
# @create 2014-12-11
# @update 2014-12-21

exports.encode = (str) ->
  try
    str = str.replace /\n/g, ''
              .replace /\s/g, ''
    window.btoa str
  catch err
    console.debug err

exports.decode = (str) ->
  try
    JSON.stringify(JSON.parse(window.atob(str)), undefined, 2)
  catch err
    console.debug err

# base64
#
# @description
# @Copyright 2014 Fantasy <fantasyshao@icloud.com>
# @create 2014-12-11
# @update 2014-12-22

exports.encode = (str) ->
  try
    new Buffer(str).toString('base64')
  catch err
    console.debug err

exports.decode = (str) ->
  try
    new Buffer(str, 'base64').toString('ascii')
  catch err
    console.debug err

Change `window.atob` methods to Node.js natvie methods.


child_process = require "child_process"

module.exports =
  run: (path, args) ->
    # TODO: Add support killing the process.
    proc = child_process.exec(path + " " + args.join " ")

    proc.on 'close', (code, signal) ->
      if code == 0
        # TODO: Display a more visible success message.
        console.info "Success!"
      else
        # TODO: Introduce proper error and warning handling.
        console.error "TeXification failed! Check the log file for more info..."

    proc

child_process = require "child_process"

module.exports =
  run: (path, args) ->
    # TODO: Add support killing the process.
    proc = child_process.exec(path + " " + args.join " ")

    proc.on "close", (code, signal) =>
      if code == 0
        # TODO: Display a more visible success message.
        console.info "Success!"
      else
        # TODO: Introduce proper error and warning handling.
        console.error "TeXification failed! Check the log file for more info..."

    proc

Change single quotes to double

Change single quotes to double


jQuery ($) ->
  EditableForm = $.fn.editableform.Constructor
  EditableForm.prototype.saveWithUrlHook = (value) ->
    originalUrl = @options.url
    model = @options.model
    nestedName = @options.nested
    nestedId =  @options.nid
    nestedLocale = @options.locale
    @options.url = (params) =>
      if typeof originalUrl == 'function'
        originalUrl.call(@options.scope, params)
      else if originalUrl? && @options.send != 'never'
        myName = params.name
        if typeof params.value == 'string'
          myValue = params.value.replace(/(\r\n|\n|\r)/gm,"<br/>")
        else
          myValue = params.value
        obj = {}
        if nestedName
          nested = {}
          nested[myName] = myValue
          nested['id'] = nestedId
          if nestedLocale
            nested['locale'] = nestedLocale
          obj[nestedName + '_attributes'] = nested
        else
          obj[myName] = myValue
        params[model] = obj
        delete params.name
        delete params.value
        delete params.pk
        $.ajax($.extend({
          url     : originalUrl
          data    : params
          type    : 'PUT'
          dataType: 'json'
        }, @options.ajaxOptions))
    @saveWithoutUrlHook(value)
  EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
  EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook

jQuery ($) ->
  EditableForm = $.fn.editableform.Constructor
  EditableForm.prototype.saveWithUrlHook = (value) ->
    originalUrl   = @options.url
    model         = @options.model
    nestedName    = @options.nested
    nestedId      = @options.nid
    nestedLocale  = @options.locale

    @options.url = (params) =>
      if typeof originalUrl == 'function'
        originalUrl.call(@options.scope, params)
      else if originalUrl? && @options.send != 'never'
        myName = params.name

        if typeof params.value == 'string'
          myValue = params.value.replace(/(\r\n|\n|\r)/gm,"<br/>")
        else
          myValue = params.value

        # if there are no values in a list, add a blank value so Rails knows all values were removed
        if Object.prototype.toString.call(params.value) == '[object Array]' && params.value.length == 0
          params.value.push("")

        obj = {}

        if nestedName
          nested          = {}
          nested[myName]  = myValue
          nested['id']    = nestedId

          if nestedLocale
            nested['locale'] = nestedLocale

          obj[nestedName + '_attributes'] = nested
        else
          obj[myName] = myValue

        params[model] = obj

        delete params.name
        delete params.value
        delete params.pk

        $.ajax($.extend({
          url:      originalUrl
          data:     params
          type:     'PUT'
          dataType: 'json'
        }, @options.ajaxOptions))

    @saveWithoutUrlHook(value)

  EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
  EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook

Add a blank value to an empty array to make sure the param is submitted

This is what Rails does -- submit a blank value to indicate that no
values were submitted to the controller.


Window = process.atom_binding('window').Window

module.exports = Window

Window = process.atom_binding('window').Window

# Convient accessors.
setupGetterAndSetter = (constructor, name, getter, setter) ->
  constructor.prototype.__defineGetter__ name, ->
    this[getter].apply(this, arguments)
  constructor.prototype.__defineSetter__ name, ->
    this[setter].apply(this, arguments)

setupGetterAndSetter Window, 'fullscreen', 'isFullscreen', 'setFullscreen'
setupGetterAndSetter Window, 'size', 'getSize', 'setSize'
setupGetterAndSetter Window, 'maximumSize', 'getMaximumSize', 'setMaximumSize'
setupGetterAndSetter Window, 'minimumSize', 'getMinimumSize', 'setMinimumSize'
setupGetterAndSetter Window, 'resizable', 'isResizable', 'setResizable'
setupGetterAndSetter Window, 'alwaysOnTop', 'isAlwaysOnTop', 'setAlwaysOnTop'
setupGetterAndSetter Window, 'position', 'getPosition', 'setPosition'
setupGetterAndSetter Window, 'title', 'getTitle', 'setTitle'
setupGetterAndSetter Window, 'kiosk', 'isKiosk', 'setKiosk'

module.exports = Window

Add convenient accessors for Window API.


React = require 'react'
window.React = React
React.initializeTouchEvents true

router = require './router'
mainContainer = document.createElement 'div'
mainContainer.id = 'panoptes-main-container'
document.body.appendChild mainContainer

if process.env.NON_ROOT isnt 'true' and location.hash isnt ""
  location.pathname = location.hash.slice(1)

router.run (Handler, handlerProps) ->
  window.dispatchEvent new Event 'locationchange'
  React.render(<Handler {...handlerProps} />, mainContainer);

logDeployedCommit = require './lib/log-deployed-commit'
logDeployedCommit()

React = require 'react'
window.React = React
React.initializeTouchEvents true

router = require './router'
mainContainer = document.createElement 'div'
mainContainer.id = 'panoptes-main-container'
document.body.appendChild mainContainer

if process.env.NON_ROOT isnt 'true' and location.hash isnt ""
  location.pathname = location.hash.slice(1)

router.run (Handler, handlerProps) ->
  window.dispatchEvent new CustomEvent 'locationchange'
  React.render(<Handler {...handlerProps} />, mainContainer);

logDeployedCommit = require './lib/log-deployed-commit'
logDeployedCommit()

Change to CustomEvent for IE 11

Change to CustomEvent for IE 11


window.JobsListView = class JobsListView extends Backbone.View

  initialize: ->
    @model.bind 'add', => this.render()

    $(@el).append('
Jobs List
')

    @list = $('
').appendTo(@el)

    $(@el).append('
Save jobs here by clicking "Add to jobs list" on a job.')

    $(window).bind 'resize', => this.setHeight()

  setHeight: ->
    height = $(window).height() - 350;
    $(@list).css(maxHeight: height)

  render: ->
    @list.empty()

    _.each(jobsList.models, (job) ->
      jobView = new JobView(model: job)
      @list.append(jobView.render().el)
    , this)

    this.setHeight()

    return this

window.JobsListView = class JobsListView extends Backbone.View

  events:
    'click p.clear-all a': 'clearAll'

  initialize: ->
    @model.bind 'add', => this.render()
    @model.bind 'remove', => this.render()

    $(@el).append('
Jobs List
')

    @list = $('
').appendTo(@el)

    $(@el).append('
Save jobs here by clicking "Add to jobs list" on a job.')

    $(@el).append('
Clear
')

    $(window).bind 'resize', => this.setHeight()

  setHeight: ->
    height = $(window).height() - 350;
    $(@list).css(maxHeight: height)

  clearAll: ->
    if confirm("Are you sure you want to remove all the saved jobs in your jobs list?")
      _.each(jobsList.models.slice(0), (job) -> job.destroy())

    return false

  render: ->
    @list.empty()

    _.each(jobsList.models, (job) ->
      jobView = new JobView(model: job)
      @list.append(jobView.render().el)
    , this)

    if jobsList.models.length > 0
      $(@el).find('p.clear-all').show()
    else
      $(@el).find('p.clear-all').hide()

    this.setHeight()

    return this

Clear all the jobs link.

Clear all the jobs link.


_ = require 'underscore'
Promise = require 'bluebird'
FetchStocks = require '../lib/fetchstocks'
{ExtendedLogger} = require 'sphere-node-utils'
Config = require '../config'

describe 'FetchStocks', ->

  beforeEach ->
    @logger = new ExtendedLogger
    @fetchStocks = new FetchStocks @logger, Config, 'anyKey'

  it 'should get the channel Information', (done) ->
    spyOn(@fetchStocks.client.channels, 'fetch').andCallFake -> Promise.resolve
      body:
        results: [
          {
            id: '123'
          }]
    @fetchStocks._getChannelId()
    .then (result) =>
      expect(result).toEqual '123'
      done()
    .catch (e) -> done e

_ = require 'underscore'
Promise = require 'bluebird'
FetchStocks = require '../lib/fetchstocks'
{ExtendedLogger} = require 'sphere-node-utils'
Config = require '../config'

describe 'FetchStocks', ->

  beforeEach ->
    @logger = new ExtendedLogger
    @fetchStocks = new FetchStocks @logger, Config

  it 'should get the channel Information', (done) ->
    spyOn(@fetchStocks.client.channels, 'fetch').andCallFake -> Promise.resolve
      body:
        results: [
          {
            id: '123'
          }]
    @fetchStocks._getChannelId()
    .then (result) =>
      expect(result).toEqual '123'
      done()
    .catch (e) -> done e

  it 'should get the stock Information', (done) ->
    spyOn(@fetchStocks.client.inventoryEntries, 'fetch').andCallFake -> Promise.resolve
      body:
        results: [
          {
            sku: '123',
            quantityOnStock: 5
          }]
    @fetchStocks.run()
    .then (result) =>
      expect(result.body.results.length).toEqual 1
      expect(result.body.results[0].sku).toEqual '123'
      expect(result.body.results[0].quantityOnStock).toEqual 5
      done()
    .catch (e) -> done e

Add tests for fetch stocks.

Add tests for fetch stocks.


class PG.Palette

  colors:
    red:
      stroke: "#b64639"
      fill: "#dc6355"
    orange:
      stroke: "#b27a01"
      fill: "#e3b514"
    yellowGreen:
      stroke: "#9bac0c"
      fill: "#c3d52a"
    lime:
      stroke: "#60a121"
      fill: "#8cd347"
    mediumSeaGreen:
      stroke: "#1a9853"
      fill: "#47d386"
    turquoise:
      stroke: "#188a91"
      fill: "#47cbd3"
    dodgerBlue:
      stroke: "#1675ab"
      fill: "#39a9e9"
    violet:
      stroke: "#5d5bbb"
      fill: "#8f8dee"
    orchid:
      stroke: "#5d5bbb"
      fill: "#8f8dee"
    violetRed:
      stroke: "#b42b58"
      fill: "#ed789e"
  fallbackColor:
    stroke: "#ccc"
    fill: "#ccc"

  color: ->
    unused = _.compact _(@colors).map (colors, name) =>
      name unless @colors[name].used
    if unused[0]
      @colors[unused[0]].used = yes
      @colors[unused[0]]
    else
      @fallbackColor

class PG.Palette

  defaults:
    red:
      stroke: "#b64639"
      fill: "#dc6355"
    orange:
      stroke: "#b27a01"
      fill: "#e3b514"
    yellowGreen:
      stroke: "#9bac0c"
      fill: "#c3d52a"
    lime:
      stroke: "#60a121"
      fill: "#8cd347"
    mediumSeaGreen:
      stroke: "#1a9853"
      fill: "#47d386"
    turquoise:
      stroke: "#188a91"
      fill: "#47cbd3"
    dodgerBlue:
      stroke: "#1675ab"
      fill: "#39a9e9"
    violet:
      stroke: "#5d5bbb"
      fill: "#8f8dee"
    orchid:
      stroke: "#5d5bbb"
      fill: "#8f8dee"
    violetRed:
      stroke: "#b42b58"
      fill: "#ed789e"

  fallbackColor:
    stroke: "#ccc"
    fill: "#ccc"

  constructor: (colors = {}) ->
    @colors = $.extend yes, colors, @defaults

  color: ->
    unused = _.compact _(@colors).map (colors, name) =>
      name unless @colors[name].used
    if unused[0]
      @colors[unused[0]].used = yes
      @colors[unused[0]]
    else
      @fallbackColor

Make sure all palette instances use their own set of used colors


@Campaigns = new Meteor.Collection 'campaigns'
@Campaigns.attachSchema new SimpleSchema
	name:
		type: String
		label: "Nome"
		max: 100
	description:
		type: String
		label: "Descrição"
		max: 10000
	createdAt:
		type: Date
		label: "Criado em"
		autoValue: ->
			if @isInsert
				return new Date
			if @isUpsert
				return $setOnInsert: new Date
			@unset()

	mainMedia:
		type: Object
		label: 'Media'
		optional: true
	'mainMedia.type':
		type: String
		allowedValues: ['Person', 'Organization'],
	'mainMedia.url':
		type: String
		regEx: SimpleSchema.RegEx.Url
	'mainMedia.videoUrl':
		type: String
		regEx: SimpleSchema.RegEx.Url
		optional: true

	user:
		type: Object
		label: 'Criado por'
		autoValue: ->
			return {
				_id: @userId
				name: Users.findOne(@userId).profile.name or Users.findOne(@userId).emails[0].address
				type: Users.findOne(@userId).profile.type
			}
	'user._id':
		type: String
	'user.name':
		type: String

### Users
	@param email
	@param password
	@param name
	@param type [organization, person]
###
@Users = Meteor.users

@Campaigns = new Meteor.Collection 'campaigns'
@Campaigns.attachSchema new SimpleSchema
	name:
		type: String
		label: "Nome"
		max: 100
	description:
		type: String
		label: "Descrição"
		max: 10000
	createdAt:
		type: Date
		label: "Criado em"
		autoValue: ->
			if @isInsert
				return new Date
			if @isUpsert
				return $setOnInsert: new Date
			@unset()

	mainMedia:
		type: Object
		label: 'Media'
		optional: true
	'mainMedia.type':
		type: String
		allowedValues: ['Person', 'Organization'],
	'mainMedia.url':
		type: String
		regEx: SimpleSchema.RegEx.Url
	'mainMedia.videoUrl':
		type: String
		regEx: SimpleSchema.RegEx.Url
		optional: true

	user:
		type: Object
		label: 'Criado por'
		autoValue: ->
			if @value then return @value
			return {
				_id: @userId
				name: Users.findOne(@userId).profile.name or Users.findOne(@userId).emails[0].address
				type: Users.findOne(@userId).profile.type
			}
	'user._id':
		type: String
	'user.name':
		type: String

### Users
	@param email
	@param password
	@param name
	@param type [organization, person]
###
@Users = Meteor.users

Fix user validation in campaign

Fix user validation in campaign