FactlinkJailRoot.showShouldSelectTextNotification = ->
  showNotification
    message: 'To create an annotation, select a statement and click the Factlink button.'
    type_classes: 'fl-message-icon-add'

FactlinkJailRoot.showLoadedNotification = ->
  showNotification
    message: 'Factlink is loaded!'
    type_classes: 'fl-message-icon-time fl-message-success'

in_screen_time = 3000
removal_delay = 1000 # Should be larger than notification_transition_time
content = """

""".trim()

showNotification = (options) ->
  $el = $(content)

  setMessage = -> $el.find('.fl-js-message').text(options.message)

  render = ->
    $el.addClass(options.type_classes)
    setMessage()
    FactlinkJailRoot.$factlinkCoreContainer.append($el)
    positionElement()

    $el.addClass 'factlink-control-visible'
    setTimeout(remove, in_screen_time)

  positionElement = ->
    $el.css
      top: '65px',
      left: '50%',
      marginLeft: "-#{$el.width()/2}px"

  remove = ->
    $el.removeClass 'factlink-control-visible'
    setTimeout (-> $el.remove()), removal_delay

  render()
  return

FactlinkJailRoot.showShouldSelectTextNotification = ->
  showNotification
    message: 'To create an annotation, select a statement and click the Factlink button.'
    type_classes: 'fl-message-icon-add'

FactlinkJailRoot.showLoadedNotification = ->
  showNotification
    message: 'Factlink is loaded!'
    type_classes: 'fl-message-icon-time fl-message-success'

in_screen_time = 3000
removal_delay = 350 # Should be larger than notification_transition_time
content = """

""".trim()

showNotification = (options) ->
  $el = $(content)

  setMessage = -> $el.find('.fl-js-message').text(options.message)

  render = ->
    $el.addClass(options.type_classes)
    setMessage()
    FactlinkJailRoot.$factlinkCoreContainer.append($el)
    positionElement()

    $el.addClass 'factlink-control-visible'
    setTimeout(remove, in_screen_time)

  positionElement = ->
    $el.css
      top: '65px',
      left: '50%',
      marginLeft: "-#{$el.width()/2}px"

  remove = ->
    $el.removeClass 'factlink-control-visible'
    setTimeout (-> $el.remove()), removal_delay

  render()
  return

Trim remove_delay down closer to transition duration


PaletteStore       = require './palette-store'
GraphStore         = require './graph-store'

nodeActions = Reflux.createActions(
  [
    "nodesChanged"
  ]
)

nodeStore   = Reflux.createStore
  listenables: [nodeActions]

  init: ->
    @nodes               = []
    @paletteItemHasNodes = false
    @selectedPaletteItem = null

    PaletteStore.store.listen @paletteChanged
    GraphStore.store.listen  @graphChanged

  onNodesChanged: (nodes) ->
    @nodes = nodes
    @internalUpdate()

  graphChanged: (status) ->
    @nodes = status.nodes
    @internalUpdate()

  paletteChanged: ->
    @selectedPaletteItem = PaletteStore.store.selectedPaletteItem
    @internalUpdate()

  internalUpdate: ->
    @paletteItemHasNodes = false
    return unless @selectedPaletteItem
    _.each @nodes, (node) =>
      if node.paletteItemIs @selectedPaletteItem
        @paletteItemHasNodes = true
    @notifyChange()

  notifyChange: ->
    data =
      nodes: @nodes
      paletteItemHasNodes: @paletteItemHasNodes
    @trigger(data)

mixin =
  getInitialState: ->
    nodes: nodeStore.nodes
    paletteItemHasNodes: nodeStore.paletteItemHasNodes

  componentDidMount: ->
    nodeStore.listen @onNodesChange

  onNodesChange: (status) ->
    @setState
      # nodes: status.nodes
      paletteItemHasNodes: status.paletteItemHasNodes

module.exports =
  actions: nodeActions
  store: nodeStore
  mixin: mixin

PaletteStore       = require './palette-store'
GraphActions       = require '../actions/graph-actions'

nodeActions = Reflux.createActions(
  [
    "nodesChanged"
  ]
)

nodeStore   = Reflux.createStore
  listenables: [nodeActions]

  init: ->
    @nodes               = []
    @paletteItemHasNodes = false
    @selectedPaletteItem = null

    PaletteStore.store.listen @paletteChanged
    GraphActions.graphChanged.listen  @graphChanged

  onNodesChanged: (nodes) ->
    @nodes = nodes
    @internalUpdate()

  graphChanged: (status) ->
    @nodes = status.nodes
    @internalUpdate()

  paletteChanged: ->
    @selectedPaletteItem = PaletteStore.store.selectedPaletteItem
    @internalUpdate()

  internalUpdate: ->
    @paletteItemHasNodes = false
    return unless @selectedPaletteItem
    _.each @nodes, (node) =>
      if node.paletteItemIs @selectedPaletteItem
        @paletteItemHasNodes = true
    @notifyChange()

  notifyChange: ->
    data =
      nodes: @nodes
      paletteItemHasNodes: @paletteItemHasNodes
    @trigger(data)

mixin =
  getInitialState: ->
    nodes: nodeStore.nodes
    paletteItemHasNodes: nodeStore.paletteItemHasNodes

  componentDidMount: ->
    nodeStore.listen @onNodesChange

  onNodesChange: (status) ->
    @setState
      # nodes: status.nodes
      paletteItemHasNodes: status.paletteItemHasNodes

module.exports =
  actions: nodeActions
  store: nodeStore
  mixin: mixin

Fix regression: Deleting items from the pallete doesn't replace them.

[#105213716]

https://www.pivotaltracker.com/story/show/105213716


class FSFolder extends FSFile

  fetchContents:(callback, dontWatch=yes)->
    { treeController } = @getOptions()

    @emit "fs.job.started"
    @vmController.run
      method     : 'fs.readDirectory'
      vmName     : @vmName
      withArgs   :
        onChange : if dontWatch then null else (change)=>
          FSHelper.folderOnChange @vmName, @path, change, treeController
        path     : FSHelper.plainPath @path
    , (err, response)=>
      if not err and response?.files
        files = FSHelper.parseWatcher @vmName, @path, response.files, treeController
        @registerWatcher response
        @emit "fs.job.finished", err, files
      else
        @emit "fs.job.finished", err
      callback? err, files

  save:(callback)->

    @emit "fs.save.started"

    @vmController.run
      vmName    : @vmName
      method    : 'fs.createDirectory'
      withArgs  :
        path    : FSHelper.plainPath @path
    , (err, res)=>
      if err then warn err
      @emit "fs.save.finished", err, res
      callback? err, res

  saveAs:(callback)->
    log 'Not implemented yet.'
    callback? null

  remove:(callback)->
    @off 'fs.delete.finished'
    @on  'fs.delete.finished', =>
      return  unless finder = KD.getSingleton 'finderController'
      finder.stopWatching @path

    super callback, yes

  registerWatcher:(response)->
    {@stopWatching} = response
    finder = KD.getSingleton 'finderController'
    return unless finder
    finder.registerWatcher @path, @stopWatching  if @stopWatching

class FSFolder extends FSFile

  fetchContents:(callback, dontWatch=yes)->
    { treeController } = @getOptions()

    @emit "fs.job.started"
    @vmController.run
      method     : 'fs.readDirectory'
      vmName     : @vmName
      withArgs   :
        onChange : if dontWatch then null else (change)=>
          FSHelper.folderOnChange @vmName, @path, change, treeController
        path     : FSHelper.plainPath @path
    , (err, response)=>
      if not err and response?.files
        files = FSHelper.parseWatcher @vmName, @path, response.files, treeController
        @registerWatcher response
        @emit "fs.job.finished", err, files
      else
        @emit "fs.job.finished", err
      callback? err, files

  save:(callback)->

    @emit "fs.save.started"

    @vmController.run
      vmName    : @vmName
      method    : 'fs.createDirectory'
      withArgs  :
        path    : FSHelper.plainPath @path
    , (err, res)=>
      if err then warn err
      @emit "fs.save.finished", err, res
      callback? err, res

  saveAs:(callback)->
    log 'Not implemented yet.'
    callback? null

  remove:(callback)->
    @off 'fs.delete.finished'
    @on  'fs.delete.finished', =>
      finder = @treeController.delegate
      finder?.stopWatching @path

    super callback, yes

  registerWatcher:(response)->
    {@stopWatching} = response
    finder = @treeController.delegate
    finder?.registerWatcher @path, @stopWatching  if @stopWatching

FSFolder: Use treeController's delegate as FinderController since there is no more FinderController singleton


Parse = require 'parse'
Sort = require 'sortablejs'
{updateSortOrder} = require '../../imports/helpers'
{Question} = require '../../imports/models'

Template.questions.onCreated ->
  @fetched = new ReactiveVar false
  @survey = @data.survey
  @questions = @data.questions
  @form = @data.form
  instance = @
  @form.getQuestions(true, @questions).then (questions) ->
    instance.fetched.set true
    instance.questions = questions

Template.questions.onRendered ->
  instance = @
  instance.autorun ->
    if instance.fetched.get() and instance.questions?.findOne()
      Meteor.defer ->
        Sort.create questionList,
          handle: '.sortable-handle'
          onSort: (event) ->
            updateSortOrder event, instance.form, 'questions'

Template.questions.helpers
  surveyId: ->
    Template.instance().survey.id
  formId: ->
    Template.instance().form.id
  hasQuestions: ->
    Template.instance().questions?.findOne()
  questions: ->
    Template.instance().questions?.find {}, sort: {order: 1}

Template.questions.events
  'click .delete': (event, instance) ->
    query = new Parse.Query Question
    query.get(@parseId).then (question) =>
      question.destroy().then () =>
        instance.questions.remove @_id

Remove required Parse causing error on question delete


#= require test_helper

emq.globalize()
ETahi.setupForTesting()
ETahi.injectTestHelpers()
ETahi.Resolver = Ember.DefaultResolver.extend namespace: ETahi
setResolver ETahi.Resolver.create()

ETahi.setupForTesting()

moduleForModel 'paper', 'Unit: Paper Model'
moduleForModel 'user', 'Unit: User Model'

test 'displayTitle displays short title if title is missing', ->
  paper = @subject
    title: ''
    shortTitle: 'test short title'
  displayTitle = paper.get('displayTitle')
  equal displayTitle, 'test short title'

#= require test_helper

emq.globalize()
ETahi.injectTestHelpers()
ETahi.Resolver = Ember.DefaultResolver.extend namespace: ETahi
setResolver ETahi.__container__

ETahi.setupForTesting()

moduleForModel 'paper', 'Unit: Paper Model',
  needs: ['model:user', 'model:declaration', 'model:figure', 'model:journal', 'model:phase']

test 'displayTitle displays short title if title is missing', ->
  debugger
  paper = @subject
    title: ''
    shortTitle: 'test short title'
  equal paper.get('displayTitle'), 'test short title'

  paper.setProperties
    title: 'Hello world'

  equal paper.get('displayTitle'), 'Hello world'

Use preferred way of testing computed properties


require 'postmortem/register'

path = require 'path'

# Ensure local node_modules bin is on the front of $PATH
binPath = path.join process.cwd(), 'node_modules/', '.bin'

process.env.PATH = ([binPath].concat process.env.PATH.split ':').join ':'

global.cp      = require './cp'
global.exec    = require 'executive'
global.invoke  = require './invoke'
global.running = require './running'
global.task    = require './task'
global.tasks   = require './tasks'
global.use     = require './use'

module.exports =
  cp:      cp
  exec:    exec
  invoke:  invoke
  running: running
  task:    task
  tasks:   tasks
  use:     use

require 'postmortem/register'
require 'vigil'

path = require 'path'

# Ensure local node_modules bin is on the front of $PATH
binPath = path.join process.cwd(), 'node_modules/', '.bin'

process.env.PATH = ([binPath].concat process.env.PATH.split ':').join ':'

global.cp      = require './cp'
global.exec    = require 'executive'
global.invoke  = require './invoke'
global.running = require './running'
global.task    = require './task'
global.tasks   = require './tasks'
global.use     = require './use'
global.walk    = vigil.walk
global.watch   = vigil.watch

module.exports =
  cp:      cp
  exec:    exec
  invoke:  invoke
  running: running
  task:    task
  tasks:   tasks
  use:     use

Add default walk and watch options.

Add default walk and watch options.


"*":
  "atom-beautify":
    analytics: false
  "autocomplete-plus":
    autoActivationDelay: 150
  core:
    telemetryConsent: "no"
    themes: [
      "atom-dark-ui"
      "atom-dark-syntax"
    ]
  editor:
    fontSize: 12
    invisibles: {}
    showIndentGuide: true
    showInvisibles: true
  "exception-reporting":
    userId: "f8b42441-9e5a-b0a5-3076-474dbe6b2797"
  "gist-it":
    newGistsDefaultToPrivate: true
  linter:
    errorPanelHeight: 160
    showErrorPanel: false
  "linter-flake8":
    ignoreErrorCodes: [
      "E128"
      "E501"
    ]
  "linter-pydocstyle":
    ignoreCodes: "D100,D101,D102,D103,D104,D105"
  "linter-ui-default": {}
  welcome:
    showOnStartup: false

"*":
  "atom-beautify":
    analytics: false
  "autocomplete-plus":
    autoActivationDelay: 150
  core:
    telemetryConsent: "no"
    themes: [
      "atom-dark-ui"
      "atom-dark-syntax"
    ]
  editor:
    fontSize: 12
    preferredLineLength: 79
    invisibles: {}
    showIndentGuide: true
    showInvisibles: true
  "exception-reporting":
    userId: "f8b42441-9e5a-b0a5-3076-474dbe6b2797"
  "gist-it":
    newGistsDefaultToPrivate: true
  linter:
    errorPanelHeight: 160
    showErrorPanel: false
  "linter-flake8":
    ignoreErrorCodes: [
      "E128"
      "E501"
    ]
  "linter-pydocstyle":
    ignoreCodes: "D100,D101,D102,D103,D104,D105"
  "linter-ui-default": {}
  welcome:
    showOnStartup: false

Set line length to 79 (per PEP8)

Set line length to 79 (per PEP8)

Feel the :heart: @jianbin-wei


selenium = require 'selenium-webdriver'
chai = require 'chai'
chai.use require 'chai-as-promised'
expect = chai.expect

before ->
	@driver = new selenium.Builder()
		.withCapabilities(selenium.Capabilities.phantomjs())
		.build()
	@driver.getWindowHandle()

after ->
	@driver.quit()

describe 'Totodoo App', ->

	@timeout 30000

	beforeEach ->
		@driver.get 'http://127.0.0.1/'

	it 'has the title of the application in the window\'s title', (done) ->
		expect(@driver.getTitle()).to.eventually.contain 'Totodoo - Sample todo application with StormPath user management service'
		@timeout 10000, done()

selenium = require 'selenium-webdriver'
chai = require 'chai'
chai.use require 'chai-as-promised'
expect = chai.expect

before ->
	@driver = new selenium.Builder()
		.withCapabilities(selenium.Capabilities.phantomjs())
		.build()
	@driver.getWindowHandle()

after ->
	@driver.quit()

describe 'Totodoo App', ->

	@timeout 12000

	beforeEach ->
		@driver.get 'http://127.0.0.1/'

	describe 'Init', ->

		it 'has the title of the application in the window\'s title', (done) ->
			expect(@driver.getTitle()).to.eventually.contain
			'Totodoo - Sample todo application with StormPath user management service'
			@timeout 4000, done()

Revert to the last working test

Revert to the last working test


angular.module('greenmine.filters.common', []).
    filter('onlyVisible', ->
        return (input) ->
            return _.filter input, (item) ->
                return item.__hidden != true
    ).
    filter('truncate', ->
        return (input, num) ->
            num = 25 if num == undefined
            return _.str.prune(input, num)
    ).
    filter('slugify', ->
        return (input) ->
            return _.str.slugify(input)
    ).
    filter("momentFormat", ->
        return (input, format) ->
            return moment(input).format(format)
    ).
    filter("lowercase", ->
        return (input) ->
            if input
                return input.toLowerCase()
            return ""
    )

OnlyVisibleFilter = ->
    return (input) ->
        return _.filter input, (item) ->
            return item.__hidden != true

TruncateFilter = ->
    return (input, num) ->
        num = 25 if num is undefined
        return _.str.prune(input, num)

SlugifyFilter = ->
    return (input) ->
        return _.str.slugify(input)

Add partial refactor of filters.

Add partial refactor of filters.


gutil         = require 'gulp-util'
child_process = require 'child_process'

module.exports = (name, command, callback = ->) ->
  child_process.exec(
    command
    (error, stdout, stderr) ->
      if error and error.code
        customError =
          message: "Failed: #{name}"

        gutil.log stdout
        gutil.log stderr
        gutil.log gutil.colors.red error
        gutil.log gutil.colors.red customError.message
        gutil.beep()
        if process.env.NODE_ENV isnt 'workstation'
          process.exit 1
        else
          callback()

      else
        gutil.log stdout
        gutil.log stderr
        gutil.log gutil.colors.green "Finished: #{name}"
        callback()
  )

gutil         = require 'gulp-util'
child_process = require 'child_process'

module.exports = (name, command, callback = ->) ->
  child_process.exec(
    command
    (error, stdout, stderr) ->
      if error and error.code
        customError =
          message: "Failed: #{name}"

        gutil.log stdout
        gutil.log stderr
        gutil.log gutil.colors.red error
        gutil.log gutil.colors.red customError.message
        gutil.beep()
        if process.env.CI
          process.exit 1
        else
          callback()

      else
        gutil.log stdout
        gutil.log stderr
        gutil.log gutil.colors.green "Finished: #{name}"
        callback()
  )

Change ci-env-detection from "workstation" to "CI"


class ActiveAdmin.PerPage
  constructor: (@options, @element)->
    @$element = $(@element)
    @_init()
    @_bind()

  _init: ->
    @$params = @_queryParams()

  _bind: ->
    @$element.change =>
      @$params['per_page'] = @$element.val()
      delete @$params['page']
      location.search = $.param(@$params)

  _queryParams: ->
    query = window.location.search.substring(1)
    params = {}
    re = /([^&=]+)=([^&]*)/g
    while m = re.exec(query)
      params[decodeURIComponent(m[1])] = decodeURIComponent(m[2])
    params

$.widget.bridge 'perPage', ActiveAdmin.PerPage

$ ->
  $('.pagination_per_page select').perPage()

class ActiveAdmin.PerPage
  constructor: (@options, @element)->
    @$element = $(@element)
    @_init()
    @_bind()

  _init: ->
    @$params = @_queryParams()

  _bind: ->
    @$element.change =>
      @$params['per_page'] = @$element.val()
      delete @$params['page']
      location.search = $.param(@$params)

  _queryParams: ->
    query = window.location.search.substring(1)
    params = {}
    re = /([^&=]+)=([^&]*)/g
    while m = re.exec(query)
      params[@_decode(m[1])] = @_decode(m[2])
    params

  _decode: (value) ->
    #replace "+" before decodeURIComponent
    decodeURIComponent(value.replace(/\+/g, '%20'))

$.widget.bridge 'perPage', ActiveAdmin.PerPage

$ ->
  $('.pagination_per_page select').perPage()

Remove plus sign (+) in URL query string while per_page switching


# 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/behind-atom-keymaps-in-depth
'atom-text-editor':
  'ctrl-shift-a': 'rubocop:autocorrect-current-file'

# 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/behind-atom-keymaps-in-depth
'atom-text-editor':
  'alt-shift-a': 'rubocop:autocorrect-current-file'

Change default shortcut to 'alt-shift-a'


define ['angular', 'underscore', 'underscoreString'], (angular, _, _s) ->
    _.mixin _s.exports()

    module = angular.module 'coviolations.models', []

    getTaskModel = ($http) ->
        class Tasks
            ### Tasks model ###

            constructor: (@limit=20, @options={}) ->
                @items = []
                @canLoad = true
                @offset = 0

            load: (callback=->@) ->
                if @canLoad
                    $http.get(@getUrl()).success (data) =>
                        @onLoaded(data, callback)
                    @offset += @limit

            getUrl: ->
                base = [
                    '/api/v1/tasks/task/',
                    '?limit=', @limit, '&offset=', @offset,
                ]
                @addOption base, 'withViolations', 'with_violations'
                @addOption base, 'self'
                @addOption base, 'project'
                @addOption base, 'branch'
                base.join('')

            addOption: (base, option, uriName=option) ->
                if @options[option]
                    base.push _.sprintf '&%s=', uriName
                    base.push @options[option]

            onLoaded: (data, callback) ->
                _.each data.objects, (item) =>
                    @items.push @prepareItem item
                if data.meta.total_count <= @offset
                    @canLoad = false
                callback.call @

            prepareItem: (item) ->
                item.created = item.created.replace('T', ' ').slice(0, -7)
                item
    module.factory 'Tasks', getTaskModel

    getTaskModel: getTaskModel

define ['angular', 'underscore', 'underscoreString'], (angular, _, _s) ->
    _.mixin _s.exports()

    module = angular.module 'coviolations.models', []

    getTaskModel = ($http) ->
        class Tasks
            ### Tasks model ###

            constructor: (@limit=20, @options={}) ->
                @items = []
                @canLoad = true
                @offset = 0
                @loadLock = false

            load: (callback=->@) ->
                if @canLoad
                    if @loadLock
                        setTimeout 100, => @load(callback)
                    else
                        @loadLock = true
                        $http.get(@getUrl()).success (data) =>
                            @onLoaded(data, callback)
                            @loadLock = false
                        @offset += @limit

            getUrl: ->
                base = [
                    '/api/v1/tasks/task/',
                    '?limit=', @limit, '&offset=', @offset,
                ]
                @addOption base, 'withViolations', 'with_violations'
                @addOption base, 'self'
                @addOption base, 'project'
                @addOption base, 'branch'
                base.join('')

            addOption: (base, option, uriName=option) ->
                if @options[option]
                    base.push _.sprintf '&%s=', uriName
                    base.push @options[option]

            onLoaded: (data, callback) ->
                _.each data.objects, (item) =>
                    @items.push @prepareItem item
                if data.meta.total_count <= @offset
                    @canLoad = false
                callback.call @

            prepareItem: (item) ->
                item.created = item.created.replace('T', ' ').slice(0, -7)
                item
    module.factory 'Tasks', getTaskModel

    getTaskModel: getTaskModel

Add lock for preventing concurrent tasks loading


EventEmitter = require('events').EventEmitter
MongoClient = require('mongodb').MongoClient
inherits = require('util').inherits

Mongo = (uri) ->
  EventEmitter.call @

  new MongoClient.connect uri, (err, database) =>
    throw err if err
    @db = database

    for col in ['arrangementer', 'bilder', 'grupper', 'områder', 'turer', 'steder']
      @[col] = @db.collection col

    @emit 'ready'

  @

inherits Mongo, EventEmitter

module.exports = new Mongo(process.env.MONGO_URI)

EventEmitter = require('events').EventEmitter
MongoClient = require('mongodb').MongoClient
inherits = require('util').inherits

Mongo = (uri) ->
  EventEmitter.call @

  new MongoClient.connect uri, (err, database) =>
    throw err if err
    @db = database

    for col in ['arrangementer', 'bilder', 'grupper', 'områder', 'turer', 'steder', 'api.users']
      @[col] = @db.collection col

    @emit 'ready'

  @

inherits Mongo, EventEmitter

module.exports = new Mongo(process.env.MONGO_URI)

Add short hand for api.user collection

Add short hand for api.user collection


class @UpdateIndexViewController extends @UpdateViewController

  navigation:
    nextRoute: "/update/seed"
    previousRoute: "/onboarding/device/plug"
    previousParams: {animateIntro: no}
  localizablePageSubtitle: "update.index.important_notice"

  navigatePrevious: ->
    ledger.app.setExecutionMode(ledger.app.Modes.Wallet)
    super

class @UpdateIndexViewController extends @UpdateViewController

  navigation:
    nextRoute: "/update/seed"
    #previousRoute: "/onboarding/device/plug"
    previousParams: {animateIntro: no}
  localizablePageSubtitle: "update.index.important_notice"

  navigatePrevious: ->
    ledger.app.setExecutionMode(ledger.app.Modes.Wallet)
    super

Hide previous button in update index view controller


class window.EvidenceBottomView extends Backbone.Marionette.ItemView
  className: 'evidence-bottom bottom-base'

  template: 'facts/bottom_base'

  triggers:
    'click .js-sub-comments-link': 'toggleSubCommentsList'

  ui:
    subCommentsLink:      '.js-sub-comments-link'
    subCommentsContainer: '.js-sub-comments-container'

  initialize: ->
    @count = 0
    @bindTo @model, 'change', @render, @

  templateHelpers: ->
    showTime: false
    showRepost: false
    showShare: false
    showSubComments: true
    showDiscussion: ->
      Factlink.Global.signed_in && @fact_base?
    showFactInfo: ->
      @fact_base?.scroll_to_link?
    fact_url_host: ->
      new Backbone.Factlink.Url(@fact_url).host() if @fact_url?

  onRender: ->
    @bindTo @model, 'change:sub_comments_count', @updateSubCommentsLink, @
    @updateSubCommentsLink()

  updateSubCommentsLink: ->
    @count = @model.get('sub_comments_count')

    if @count > 0
      @ui.subCommentsContainer.show()
      @ui.subCommentsLink.text "Comments (#{@count})"
    else if Factlink.Global.signed_in
      @ui.subCommentsContainer.show()
      @ui.subCommentsLink.text "Comments"
    else
      @ui.subCommentsContainer.hide()

class window.EvidenceBottomView extends Backbone.Marionette.ItemView
  className: 'evidence-bottom bottom-base'

  template: 'facts/bottom_base'

  triggers:
    'click .js-sub-comments-link': 'toggleSubCommentsList'

  ui:
    subCommentsLink:      '.js-sub-comments-link'
    subCommentsContainer: '.js-sub-comments-container'

  initialize: ->
    @count = 0
    @bindTo @model, 'change', @render, @

  templateHelpers: ->
    showTime: false
    showRepost: false
    showShare: false
    showSubComments: true
    showDiscussion: ->
      Factlink.Global.signed_in && @fact_base?
    showFactInfo: ->
      @fact_base?.scroll_to_link?
    fact_url_host: ->
      new Backbone.Factlink.Url(@fact_url).host() if @fact_url?

  onRender: ->
    @bindTo @model, 'change:sub_comments_count', @updateSubCommentsLink, @
    @updateSubCommentsLink()

  updateSubCommentsLink: ->
    @count = @model.get('sub_comments_count')

    if @count > 0
      @ui.subCommentsContainer.removeClass 'hide'
      @ui.subCommentsLink.text "Comments (#{@count})"
    else if Factlink.Global.signed_in
      @ui.subCommentsContainer.removeClass 'hide'
      @ui.subCommentsLink.text "Comments"
    else
      @ui.subCommentsContainer.addClass 'hide'

Use css class 'hide' instead of jQuery show/hide methods to prevent jQuery weirdness.


noflo = require 'noflo'

findHandler = (actionParts, routes) ->
  normalized = routes.map (route) ->
    if route.indexOf('*') is -1
      # No wildcards here
      return route
    routeParts = route.split ':'
    for part, idx in routeParts
      continue unless part is '*'
      continue unless actionParts[idx]
      routeParts[idx] = actionParts[idx]
    return routeParts.join ':'
  return normalized.indexOf actionParts.join(':')

exports.getComponent = ->
  c = new noflo.Component
  c.inPorts.add 'routes',
    datatype: 'string'
    required: true
    control: true
  c.inPorts.add 'in',
    datatype: 'all'
  c.outPorts.add 'pass',
    datatype: 'all'
  c.outPorts.add 'handle',
    datatype: 'all'
    addressable: true
  c.outPorts.add 'handling',
    datatype: 'integer'
  c.process (input, output) ->
    return unless input.hasData 'routes', 'in'
    [routes, data] = input.getData 'routes', 'in'
    unless data?.action
      output.done new Error 'No action provided in payload'
      return
    handled = routes.split ','
    handler = findHandler data.action.split(':'), handled
    if handler is -1
      output.sendDone
        pass: data
      return
    output.send
      handling: handler
    output.send
      handle: new noflo.IP 'data', data,
        index: handler
    output.done()

noflo = require 'noflo'

findHandler = (actionParts, routes) ->
  normalized = routes.map (route) ->
    if route.indexOf('*') is -1
      # No wildcards here
      return route
    routeParts = route.split ':'
    for part, idx in routeParts
      continue unless part is '*'
      continue unless actionParts[idx]
      routeParts[idx] = actionParts[idx]
    return routeParts.join ':'
  return normalized.indexOf actionParts.join(':')

exports.getComponent = ->
  c = new noflo.Component
  c.inPorts.add 'routes',
    datatype: 'string'
    required: true
    control: true
  c.inPorts.add 'in',
    datatype: 'all'
  c.outPorts.add 'pass',
    datatype: 'all'
  c.outPorts.add 'handle',
    datatype: 'all'
    addressable: true
  c.outPorts.add 'handling',
    datatype: 'integer'
  c.process (input, output) ->
    return unless input.hasData 'routes', 'in'
    [routes, data] = input.getData 'routes', 'in'
    unless data?.action
      output.sendDone
        pass: data
      return
    handled = routes.split ','
    handler = findHandler data.action.split(':'), handled
    if handler is -1
      output.sendDone
        pass: data
      return
    output.send
      handling: handler
    output.send
      handle: new noflo.IP 'data', data,
        index: handler
    output.done()

Send all unhandled to PASS port

Send all unhandled to PASS port


module.exports =
  new: (req, res) ->
    res.render('sessions/new')

  create: (passport) ->
    ->
      passport.authenticate 'local',
        successRedirect: '/'
        failureRedirect: '/login'
        successFlash: 'You have successfully logged in!'
        failureFlash: true

  destroy: (req, res) ->
    req.logout()
    req.flash 'info', 'You have successfully logged out!'
    res.redirect '/'

module.exports =
  new: (req, res) ->
    res.render('sessions/new')

  create: (passport) ->
    ->
      passport.authenticate 'local',
        successRedirect: '/'
        failureRedirect: '/login'
        successFlash: 'You have successfully logged in!'
        failureFlash: true

  destroy: (req, res) ->
    req.logout()
    req.flash 'info', 'You have successfully logged out!'
    res.redirect '/login'

Fix redirect path after loggin out

Fix redirect path after loggin out


spawn = require("child_process").spawn
logger = require "logger-sharelatex"
Settings = require "settings-sharelatex"

module.exports = DockerRunner =
  _docker: Settings.clsi?.docker?.binary or 'docker'
  _baseCommand: ['run', '--rm=true', '-t', '-v', '$COMPILE_DIR:/source', '--name=texlive-$PROJECT_ID']

  run: (project_id, command, directory, timeout, callback = (error) ->) ->
    logger.log project_id: project_id, command: command, directory: directory, "running docker command"

    if command[0] != 'latexmk'
      throw 'Invalid command'

    docker_cmd = (arg.replace('$COMPILE_DIR', directory).replace('$PROJECT_ID', project_id) \
      for arg in DockerRunner._baseCommand)
    docker_cmd.push Settings.clsi?.docker?.image or "texlive"
    docker_cmd = docker_cmd.concat (arg.replace('$COMPILE_DIR', '/source') for arg in command.slice(1))

    proc = spawn DockerRunner._docker, docker_cmd, stdio: "inherit", cwd: directory
    timer = setTimeout () ->
      logger.warn "timeout achieved, stopping docker instance"
      proc = spawn 'docker', ['stop', "texlive-#{project_id}"]
      proc.on "close", ->
        callback timedout: true
    , (timeout ?= 30) * 1000
    proc.on "close", () ->
      clearTimeout timer
      callback()

spawn = require("child_process").spawn
logger = require "logger-sharelatex"
Settings = require "settings-sharelatex"

module.exports = DockerRunner =
  _docker: Settings.clsi?.docker?.binary or 'docker'
  _baseCommand: ['run', '--rm=true', '-t', '-v', '$COMPILE_DIR:/source', '--name=texlive-$PROJECT_ID']

  run: (project_id, command, directory, timeout, callback = (error) ->) ->
    logger.log project_id: project_id, command: command, directory: directory, "running docker command"

    if command[0] != 'latexmk'
      throw 'Invalid command'

    docker_cmd = (arg.replace('$COMPILE_DIR', directory).replace('$PROJECT_ID', project_id) \
      for arg in DockerRunner._baseCommand)
    docker_cmd.push Settings.clsi?.docker?.image or "texlive"
    docker_cmd = docker_cmd.concat (arg.replace('$COMPILE_DIR', '/source') for arg in command.slice(1))

    proc = spawn DockerRunner._docker, docker_cmd, stdio: "inherit", cwd: directory
    timer = setTimeout () ->
      logger.warn "timeout achieved, stopping docker instance"
      proc = spawn 'docker', ['stop', "texlive-#{project_id}"]
      proc.on "close", ->
        callback timedout: true
    , timeout
    proc.on "close", () ->
      clearTimeout timer
      callback()

Revert the timeout change in the docker runner

Revert the timeout change in the docker runner as the conversion from seconds to milliseconds as well as the default timeout are already handled in the Request parser.


# @Compiler-Output "../Dist/ExceptedWrite.js"

EventEmitter = require('events').EventEmitter
Promise = require('a-promise')
Buffer = require('buffer').Buffer

class ExpectedWrite extends EventEmitter
  constructor: (@stream) ->
    super
    @status = true
    @expected = null
    @callback = null
    @data = stdout: new Buffer("", "utf8"), stderr: new Buffer("", "utf8")

    @stream.on 'close', =>
      @emit('end')
    if @stream.stdout
      @stream.stdout.on 'data', (data) =>
        @data.stdout = Buffer.concat([@data.stdout, data])
    else
      @stream.on 'data', (data) =>
        @data.stdout = Buffer.concat([@data.stdout, data])
    @stream.stderr.on 'data', (data) =>
      @data.stderr = Buffer.concat([@data.stderr, data])

  onEnd: ->
    return new Promise (Resolve)=>
      if @status
        @once('end', Resolve)
      else
        Resolve()

module.exports = ExpectedWrite

# @Compiler-Output "../Dist/ExceptedWrite.js"

EventEmitter = require('events').EventEmitter
Promise = require('a-promise')
Buffer = require('buffer').Buffer

class ExpectedWrite extends EventEmitter
  constructor: (@stream) ->
    super
    @status = true
    @expected = null
    @callback = null
    @data = stdout: '', stderr: ''

    @stream.on 'close', =>
      @emit('end')
    if @stream.stdout
      @stream.stdout.on 'data', (data) =>
        @data.stdout += data
    else
      @stream.on 'data', (data) =>
        @data.stdout += data
    @stream.stderr.on 'data', (data) =>
      @data.stderr += data

  onEnd: ->
    return new Promise (Resolve)=>
      if @status
        @once('end', Resolve)
      else
        Resolve()

module.exports = ExpectedWrite

Use raw strings instead of buffers

:art: Use raw strings instead of buffers


goog.provide 'server.react.App'

class server.react.App

  ###*
    @constructor
  ###
  constructor: ->
    {html,head,meta,title,link,body} = React.DOM

    @create = React.createClass
      render: ->
        html lang: 'en',
          head null,
            meta charSet: 'utf-8'
            meta name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
            title null, this.props.title
            link href: '/app/client/img/favicon.ico', rel: 'shortcut icon'
            link href: '/app/client/build/app.css?v=' + this.props.version, rel: 'stylesheet'
            # TODO(steida): Base64 inline.
            link href: 'http://fonts.googleapis.com/css?family=PT+Sans&subset=latin,latin-ext', rel: 'stylesheet'
          body dangerouslySetInnerHTML: __html: this.props.bodyHtml

goog.provide 'server.react.App'

class server.react.App

  ###*
    @constructor
  ###
  constructor: ->
    {html,head,meta,title,link,body} = React.DOM

    @create = React.createClass
      render: ->
        html lang: 'en',
          head null,
            meta charSet: 'utf-8'
            # NOTE(steida): http://www.mobilexweb.com/blog/ios-7-1-safari-minimal-ui-bugs
            meta name: 'viewport', content: 'width=device-width, initial-scale=1.0, minimal-ui, maximum-scale=1.0, user-scalable=no'
            title null, this.props.title
            link href: '/app/client/img/favicon.ico', rel: 'shortcut icon'
            link href: '/app/client/build/app.css?v=' + this.props.version, rel: 'stylesheet'
            # TODO(steida): Base64 inline.
            link href: 'http://fonts.googleapis.com/css?family=PT+Sans&subset=latin,latin-ext', rel: 'stylesheet'
          body dangerouslySetInnerHTML: __html: this.props.bodyHtml

Add minimal-ui for iOS Safari.

Add minimal-ui for iOS Safari.


module.exports =
  isLoggedIn: (req, res, next) ->
    if req.user
      next()
    else
      req.flash 'error', 'You must be logged in!'
      res.redirect '/login'

  isNotLoggedIn: (req, res, next) ->
    if req.user
      req.flash 'error', 'You are already logged in!'
      res.redirect '/'
    else
      next()

module.exports =
  isLoggedIn: (req, res, next) ->
    if req.isAuthenticated()
      next()
    else
      req.flash 'error', 'You must be logged in!'
      res.redirect '/login'

  isNotLoggedIn: (req, res, next) ->
    if req.isAuthenticated()
      req.flash 'error', 'You are already logged in!'
      res.redirect '/'
    else
      next()

Replace property with method to improve intelligibility


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

module.exports =
class StatusBarView extends View
  @content: ->
    @div class: 'status-bar tool-panel panel-bottom', =>
      @div outlet: 'rightPanel', class: 'status-bar-right pull-right'
      @div outlet: 'leftPanel', class: 'status-bar-left'

  initialize: ->
    @bufferSubscriptions = []
    @subscribe atom.rootView, 'pane-container:active-pane-item-changed', =>
      @unsubscribeAllFromBuffer()
      @storeActiveBuffer()
      @subscribeAllToBuffer()

      @trigger('active-buffer-changed')

    @storeActiveBuffer()

  attach: ->
    atom.rootView.vertical.append(this) unless @hasParent()

  appendLeft: (item) ->
    @leftPanel.append(item)

  appendRight: (item) ->
    @rightPanel.append(item)

  getActiveBuffer: ->
    @buffer

  getActiveItem: ->
    atom.rootView.getActivePaneItem()

  storeActiveBuffer: ->
    @buffer = @getActiveItem()?.getBuffer?()

  subscribeToBuffer: (event, callback) ->
    @bufferSubscriptions.push([event, callback])
    @buffer.on(event, callback) if @buffer

  subscribeAllToBuffer: ->
    return unless @buffer
    for [event, callback] in @bufferSubscriptions
      @buffer.on(event, callback)

  unsubscribeAllFromBuffer: ->
    return unless @buffer
    for [event, callback] in @bufferSubscriptions
      @buffer.off(event, callback)

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

module.exports =
class StatusBarView extends View
  @content: ->
    @div class: 'status-bar tool-panel panel-bottom', =>
      @div outlet: 'rightPanel', class: 'status-bar-right pull-right'
      @div outlet: 'leftPanel', class: 'status-bar-left'

  initialize: ->
    atom.rootView.statusBar = this

    @bufferSubscriptions = []
    @subscribe atom.rootView, 'pane-container:active-pane-item-changed', =>
      @unsubscribeAllFromBuffer()
      @storeActiveBuffer()
      @subscribeAllToBuffer()

      @trigger('active-buffer-changed')

    @storeActiveBuffer()

  attach: ->
    atom.rootView.vertical.append(this) unless @hasParent()

  appendLeft: (item) ->
    @leftPanel.append(item)

  appendRight: (item) ->
    @rightPanel.append(item)

  getActiveBuffer: ->
    @buffer

  getActiveItem: ->
    atom.rootView.getActivePaneItem()

  storeActiveBuffer: ->
    @buffer = @getActiveItem()?.getBuffer?()

  subscribeToBuffer: (event, callback) ->
    @bufferSubscriptions.push([event, callback])
    @buffer.on(event, callback) if @buffer

  subscribeAllToBuffer: ->
    return unless @buffer
    for [event, callback] in @bufferSubscriptions
      @buffer.on(event, callback)

  unsubscribeAllFromBuffer: ->
    return unless @buffer
    for [event, callback] in @bufferSubscriptions
      @buffer.off(event, callback)

Add itself as a global on rootView

Add itself as a global on rootView


Template.chart.helpers
  nonNumberDataColumnSpec: ->
    for spec in @header.slice(1)
      if spec.chartType isnt "number"
        return spec
  reactivityHack: ->
    _.defer =>
      $chart = $(".chart[data-id='" + @_id + "']")
      $chart.empty()
      $chartContainer = $("

")
      $chart.append($chartContainer)
      data = new google.visualization.DataTable()
      for spec in @header
#        if @output is "rwstats" and spec.isPercentage
#          continue
        data.addColumn(spec.chartType, i18n.t("rwcut.fields." + spec.name))
      data.addRows(@rows)
      chart = new google.visualization[@chartType]($chartContainer.get(0))
      chart.draw(data,
        curveType: "function"
        vAxis:
          logScale: true
      )

Template.chart.rendered = ->

Template.chart.events
#  "click .selector": (event, template) ->

Template.chart.helpers
  nonNumberDataColumnSpec: ->
    for spec in @header.slice(1)
      if spec.chartType isnt "number"
        return spec
  reactivityHack: ->
    _.defer =>
      $chart = $(".chart[data-id='" + @_id + "']")
      $chart.empty()
      $chartContainer = $("

")
      $chart.append($chartContainer)
      data = new google.visualization.DataTable()
      for spec in @header
        if @output is "rwstats" and (spec.isPercentage or spec.name is "cumul_%")
          continue
        data.addColumn(spec.chartType, i18n.t("rwcut.fields." + spec.name))
      for row in @rows
        values = []
        for value, index in row
          spec = @header[index]
          if @output is "rwstats" and (spec.isPercentage or spec.name is "cumul_%")
            continue
          values.push(value)
        data.addRow(values)
      chart = new google.visualization[@chartType]($chartContainer.get(0))
      chart.draw(data,
        curveType: "function"
        vAxis:
          logScale: true
      )

Template.chart.rendered = ->

Template.chart.events
#  "click .selector": (event, template) ->

Remove percentage values from chart output


AppConfig = require('../initializers/config')
Deploy = require('../lib/deploy')
range_check = require('range_check')

tagRefersToServer = (tag, serverName) ->
  return new RegExp("^#{serverName}").test(tag)

getIpFromRequest = (req) ->
  req.connection.remoteAddress

GITHUB_IP_RANGE = "192.30.252.0/22"

ipIsFromGithub = (ip) ->
  range_check.in_range(ip, GITHUB_IP_RANGE)

exports.index = (req, res) ->
  remoteIp = getIpFromRequest(req)
  envIsNotDevelopment = process.env.NODE_ENV isnt 'development'
  if !ipIsFromGithub(remoteIp) and envIsNotDevelopment
    return res.send 401

  parsedPayload = req.body

  console.log "Got deploy message from #{parsedPayload.ref}"

  serverName = AppConfig.get('server_name')
  tagName = parsedPayload.ref

  unless tagRefersToServer(tagName, serverName)
    errMessage = "Only deploys for this server (#{serverName}) are accepted"
    console.log errMessage
    return res.send 500, errMessage

  console.log "Updating code from #{tagName}..."
  Deploy.deploy(tagName).then(->
    console.log "Code update finished, restarting server"
    process.exit()
  ).catch((err)->
    console.log "Error updating code:"
    console.error err
  )

  res.send 200

AppConfig = require('../initializers/config')
Deploy = require('../lib/deploy')
range_check = require('range_check')

tagRefersToServer = (tag, serverName) ->
  return new RegExp("^#{serverName}").test(tag)

getIpFromRequest = (req) ->
  req.connection.remoteAddress

GITHUB_IP_RANGE = "192.30.252.0/22"

ipIsFromGithub = (ip) ->
  range_check.in_range(ip, GITHUB_IP_RANGE)

exports.index = (req, res) ->
  remoteIp = getIpFromRequest(req)

  env = process.env.NODE_ENV
  env ||= 'development'
  if env isnt 'development'
    return res.send 401 unless ipIsFromGithub(remoteIp)

  parsedPayload = req.body

  console.log "Got deploy message from #{parsedPayload.ref}"

  serverName = AppConfig.get('server_name')
  tagName = parsedPayload.ref

  unless tagRefersToServer(tagName, serverName)
    errMessage = "Only deploys for this server (#{serverName}) are accepted"
    console.log errMessage
    return res.send 500, errMessage

  console.log "Updating code from #{tagName}..."
  Deploy.deploy(tagName).then(->
    console.log "Code update finished, restarting server"
    process.exit()
  ).catch((err)->
    console.log "Error updating code:"
    console.error err
  )

  res.send 200

Make "don't check github ip in development" work


Hummingbird.TruncateTextComponent = Ember.Component.extend
  expanded: false

  isTruncated: (->
    @get('text').length > @get('length') + 10
  ).property('text', 'length')

  truncatedText: (->
    if @get('isTruncated') and not @get('expanded')
      jQuery.trim(@get('text')).substring(0, @get('length')).trim(this) + "..."
    else
      @get('text')
  ).property('text', 'length', 'expanded')

  actions:
    toggleExpansion: ->
      @toggleProperty 'expanded'

Hummingbird.TruncateTextComponent = Ember.Component.extend
  expanded: false

  isTruncated: (->
    @get('text').length > @get('length') + 10
  ).property('text', 'length')

  truncatedText: (->
    if @get('isTruncated') and not @get('expanded')
      jQuery.trim(@get('text')).substring(0, @get('length')).trim(this) + "…"
    else
      @get('text')
  ).property('text', 'length', 'expanded')

  actions:
    toggleExpansion: ->
      @toggleProperty 'expanded'

Use actual ellipsis unicode character for truncation.


path = require 'path'
fs = require 'fs-plus'
_  = require 'underscore-plus'

module.exports =
class PathFinder
  railsRootPathChildren: ['app', 'config', 'lib']
  ignores: /(?:\/.git\/|\.keep$|\.DS_Store$|\.eot$|\.otf$|\.ttf$|\.woff$|\.png$|\.svg$|\.jpg$|\.gif$|\.mp4$|\.eps$|\.psd$)/

  constructor: (currentPath) ->
    @railsRootPath = @getRailsRootPath(currentPath)

  getPathes: (key) ->
    return [] unless @railsRootPath

    pathes = []
    for relativeRootPath in atom.config.get("rails-finder.#{key}Pathes")
      rootPath = path.join(@railsRootPath, relativeRootPath)
      appendPathes = _.filter(fs.listTreeSync(rootPath), (candidatePath) =>
        fs.isFileSync(candidatePath) && !@ignores.test(candidatePath)
      )
      pathes = pathes.concat(appendPathes)

    return _.uniq(pathes)

  getRailsRootPath: (currentPath) ->
    candidatePath = path.dirname(currentPath)
    while candidatePath != '/'
      candidatePath = path.resolve(path.join(candidatePath, '..'))

      children = _.map(fs.listSync(candidatePath), (child) ->
        path.basename(child)
      )
      isRailsRootPath = _.all(@railsRootPathChildren, (railsRootPathChild)->
        _.contains(children, railsRootPathChild)
      )
      if isRailsRootPath
        return candidatePath
    return atom.project.rootDirectories[0].path

path = require 'path'
fs = require 'fs-plus'
_  = require 'underscore-plus'

module.exports =
class PathFinder
  railsRootPathChildren: ['app', 'config', 'lib']
  ignores: /(?:\/.git\/|\.(?:git)?keep$|\.DS_Store$|\.eot$|\.otf$|\.ttf$|\.woff$|\.png$|\.svg$|\.jpg$|\.gif$|\.mp4$|\.eps$|\.psd$)/

  constructor: (currentPath) ->
    @railsRootPath = @getRailsRootPath(currentPath)

  getPathes: (key) ->
    return [] unless @railsRootPath

    pathes = []
    for relativeRootPath in atom.config.get("rails-finder.#{key}Pathes")
      rootPath = path.join(@railsRootPath, relativeRootPath)
      appendPathes = _.filter(fs.listTreeSync(rootPath), (candidatePath) =>
        fs.isFileSync(candidatePath) && !@ignores.test(candidatePath)
      )
      pathes = pathes.concat(appendPathes)

    return _.uniq(pathes)

  getRailsRootPath: (currentPath) ->
    candidatePath = path.dirname(currentPath)
    while candidatePath != '/'
      candidatePath = path.resolve(path.join(candidatePath, '..'))

      children = _.map(fs.listSync(candidatePath), (child) ->
        path.basename(child)
      )
      isRailsRootPath = _.all(@railsRootPathChildren, (railsRootPathChild)->
        _.contains(children, railsRootPathChild)
      )
      if isRailsRootPath
        return candidatePath
    return atom.project.rootDirectories[0].path

Fix tiny bug in ignores

Fix tiny bug in ignores


`import Ember from 'ember'`

Location = Ember.HistoryLocation.extend
  init: ->
    @_super.apply this, arguments

    if auth = @get('auth')
      # location's getURL is first called before we even
      # get to routes, so autoSignIn won't be called in
      # such case
      auth.autoSignIn() unless auth.get('signedIn')

  getURL: ->
    url = this._super.apply(this, arguments)
    if location.pathname == '/'
      if @get('auth.signedIn')
        return '/repositories'
      else
        return '/home'

    url

  formatURL: (logicalPath) ->
    if logicalPath == '/repositories' || logicalPath == '/home'
      '/'
    else
      @_super.apply this, arguments

`export default Location`

`import Ember from 'ember'`
`import config from 'travis/config/environment'`

Location = Ember.HistoryLocation.extend
  init: ->
    @_super.apply this, arguments

    if auth = @get('auth')
      # location's getURL is first called before we even
      # get to routes, so autoSignIn won't be called in
      # such case
      auth.autoSignIn() unless auth.get('signedIn')

  getURL: ->
    url = this._super.apply(this, arguments)
    unless config.pro
      if location.pathname == '/'
        if @get('auth.signedIn')
          return '/repositories'
        else
          return '/home'

    url

  formatURL: (logicalPath) ->
    if logicalPath == '/repositories' || logicalPath == '/home'
      '/'
    else
      @_super.apply this, arguments

`export default Location`

Disable landing page on pro for now

Disable landing page on pro for now


jQuery ->
	myOptions =
		center: new google.maps.LatLng(-34.397, 150.644)
		zoom: 8
		mapTypeId: google.maps.MapTypeId.ROADMAP
	map = new google.maps.Map(document.getElementById("centermap"), myOptions)

$ ->
	myOptions =
		center: new google.maps.LatLng(-34.397, 150.644)
		zoom: 8
		disableDefaultUI: true
		mapTypeId: google.maps.MapTypeId.ROADMAP
	map = new google.maps.Map(document.getElementById("centermap"), myOptions)

Disable default Google Maps controls

Disable default Google Maps controls


RootView = require 'root-view'
Editor = require 'editor'
ChildProcess = require 'child_process'

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

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

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

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

      expect(ChildProcess.spawn).toHaveBeenCalled()
      expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe "http://github.com"

RootView = require 'root-view'
Editor = require 'editor'
ChildProcess = require 'child_process'

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

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

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

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

      expect(ChildProcess.spawn).toHaveBeenCalled()
      expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe "http://github.com"

Use correct textmate package names

Use correct textmate package names


ETahi.ChosenView = Ember.Select.extend
  multiple: false
  width: '200px'
  disableSearchThreshold: 0
  searchContains: true
  attributeBindings:['multiple', 'width', 'disableSearchThreshold', 'searchContains', 'data-placeholder']
  changeAction: null

  change: ->
    action = @get('changeAction')
    @get('controller').send(action, @get('value')) if action

  setup: (->
    options =
      multiple: @get('multiple')
      width: @get('width')
      disable_search_threshold: @get('disableSearchThreshold')
      search_contains: @get('searchContains')
      no_results_text: @get('noResultsText')
      max_selected_options: @get('maxSelectedOptions')
      allow_single_deselect: @get('allowSingleDeselect')
      inherit_select_classes: true

    options.clean_search_text = @cleanSearchText
    options.calling_context = @

    @.$().chosen(options)

    @addObserver @get("optionLabelPath").replace(/^content/, "content.@each"), =>
      @rerenderChosen()
  ).on('didInsertElement')

  teardown: (->
    @.$().chosen('destroy')
  ).on('willDestroyElement')

  cleanSearchText: (option, context) ->
    option.text

  rerenderChosen: ->
    # Don't trigger Chosen update until after DOM elements have finished rendering.
    Ember.run.scheduleOnce 'afterRender', @, ->
      if @.$()
        @.$().trigger('chosen:updated')

Ember.Handlebars.helper('chosen', ETahi.ChosenView)

ETahi.ChosenView = Ember.Select.extend
  multiple: false
  width: '200px'
  disableSearchThreshold: 0
  searchContains: true
  attributeBindings:['multiple', 'width', 'disableSearchThreshold', 'searchContains', 'data-placeholder']
  changeAction: null

  change: ->
    action = @get('changeAction')
    @get('controller').send(action, @get('value')) if action

  setup: (->
    options =
      multiple: @get('multiple')
      width: @get('width')
      disable_search_threshold: @get('disableSearchThreshold')
      search_contains: @get('searchContains')
      no_results_text: @get('noResultsText')
      max_selected_options: @get('maxSelectedOptions')
      allow_single_deselect: @get('allowSingleDeselect')
      inherit_select_classes: true

    options.clean_search_text = @cleanSearchText
    options.calling_context = @

    @.$().chosen(options)

    @addObserver @get("optionLabelPath").replace(/^content/, "content.@each"), =>
      @rerenderChosen()
    @addObserver "value", =>
      @rerenderChosen()
  ).on('didInsertElement')

  teardown: (->
    @.$().chosen('destroy')
  ).on('willDestroyElement')

  cleanSearchText: (option, context) ->
    option.text

  rerenderChosen: ->
    # Don't trigger Chosen update until after DOM elements have finished rendering.
    Ember.run.scheduleOnce 'afterRender', @, ->
      if @.$()
        @.$().trigger('chosen:updated')

Ember.Handlebars.helper('chosen', ETahi.ChosenView)

Fix issue where chosen wouldn't update if bound selected value changed elsewhere


{$, ScrollView} = require 'atom-space-pen-views'

module.exports =
  class AtomPythonTestView extends ScrollView

    message: ''
    maximized: false

    @content: ->
      @div class: 'atom-python-test-view native-key-bindings', outlet: 'atomTestView', tabindex: -1, overflow: "auto", =>
        @div class: 'btn-toolbar', outlet:'toolbar', =>
          @button outlet: 'closeBtn', class: 'btn inline-block-tight right', click: 'destroy', style: 'float: right', =>
            @span class: 'icon icon-x'
        @pre class: 'output', outlet: 'output'

    initialize: ->
      @panel ?= atom.workspace.addBottomPanel(item: this)

    addLine: (line) ->
      @message += line
      @find(".output").text(@message)

    clear: ->
      @message = ''

    finish: ->
      console.log('finish')

    destroy: ->
      @panel.hide()

    reset: -> @message = defaultMessage

    toggle: ->
      @find(".output").height(500)
      @addLine 'Running tests... \n'
      @panel.show()

{$, ScrollView} = require 'atom-space-pen-views'

module.exports =
  class AtomPythonTestView extends ScrollView

    message: ''
    maximized: false

    @content: ->
      @div class: 'atom-python-test-view native-key-bindings', outlet: 'atomTestView', tabindex: -1, overflow: "auto", =>
        @div class: 'btn-toolbar', outlet:'toolbar', =>
          @button outlet: 'closeBtn', class: 'btn inline-block-tight right', click: 'destroy', style: 'float: right', =>
            @span class: 'icon icon-x'
        @pre class: 'output', outlet: 'output'

    initialize: ->
      @panel ?= atom.workspace.addBottomPanel(item: this)
      @panel.hide()

    addLine: (line) ->
      @message += line
      @find(".output").text(@message)

    clear: ->
      @message = ''

    finish: ->
      console.log('finish')

    destroy: ->
      @panel.hide()

    reset: -> @message = defaultMessage

    toggle: ->
      @find(".output").height(500)
      @addLine 'Running tests... \n'
      @panel.show()

Hide atom python test panel on Atom opening.


Sprangular.controller 'AccountCtrl', ($scope, $location, $routeParams, Status, Account) ->
  Status.pageTitle = 'My Account'

  user = Account.user

  $scope.editing = false
  $scope.user = user

  refreshAccount = ->
    Account.init().then ->
      user = Account.user
      user.password = ''
      user.password_confirmation = ''

  $scope.edit = ->
    $scope.editing = true

  $scope.stopEdit = ->
    $scope.editing = false

  $scope.save = ->
    user.errors = {}

    Account.save(user)
      .then (content) ->
        $scope.editing = false
        $location.path('/') if !Account.isLogged
      , (errors) ->
        user.errors = errors

  refreshAccount()

Sprangular.controller 'AccountCtrl', ($scope, $location, $routeParams, Status, Account) ->
  Status.pageTitle = 'My Account'

  user = Account.user

  $scope.editing = false
  $scope.user = user

  refreshAccount = ->
    Account.reload().then ->
      user = Account.user
      user.password = ''
      user.password_confirmation = ''

  $scope.edit = ->
    $scope.editing = true

  $scope.stopEdit = ->
    $scope.editing = false

  $scope.save = ->
    user.errors = {}

    Account.save(user)
      .then (content) ->
        $scope.editing = false
        $location.path('/') if !Account.isLogged
      , (errors) ->
        user.errors = errors

  refreshAccount()

Use `Account.reload` instead of `init`

Use `Account.reload` instead of `init`


ETahi.InlineEditBodyPartComponent = Em.Component.extend
  editing: false
  snapshot: []
  confirmDelete: false

  createSnapshot: (->
    @set('snapshot', Em.copy(@get('block'), true))
  ).observes('editing')

  hasContent: (->
    @get('block').any(@_isEmpty)
  ).property('block.@each.value')

  hasNoContent: Em.computed.not('hasContent')

  bodyPartType: (->
    @get('block.firstObject.type')
  ).property('block.@each.type')

  _isEmpty: (item) ->
    item && !Ember.isEmpty(item.value)

  actions:
    toggleEdit: ->
      @sendAction('cancel', @get('block'), @get('snapshot')) if @get('editing')
      @toggleProperty 'editing'

    deleteBlock: ->
      @sendAction('delete', @get('block'))

    save: ->
      if @get('hasContent')
        @sendAction('save', @get('block'))
        @toggleProperty 'editing'

    confirmDeletion: ->
      @set('confirmDelete', true)

    cancelDestroy: ->
      @set('confirmDelete', false)

    addItem: ->
      @sendAction('addItem', @get('block'))

ETahi.InlineEditBodyPartComponent = Em.Component.extend
  editing: false
  snapshot: []
  confirmDelete: false

  createSnapshot: (->
    @set('snapshot', Em.copy(@get('block'), true))
  ).observes('editing')

  hasContent: (->
    @get('block').any(@_isNotEmpty)
  ).property('block.@each.value')

  hasNoContent: Em.computed.not('hasContent')

  bodyPartType: (->
    @get('block.firstObject.type')
  ).property('block.@each.type')

  _isNotEmpty: (item) ->
    item && !Em.isEmpty(item.value)

  actions:
    toggleEdit: ->
      @sendAction('cancel', @get('block'), @get('snapshot')) if @get('editing')
      @toggleProperty 'editing'

    deleteBlock: ->
      @sendAction('delete', @get('block'))

    save: ->
      if @get('hasContent')
        @sendAction('save', @get('block'))
        @toggleProperty 'editing'

    confirmDeletion: ->
      @set('confirmDelete', true)

    cancelDestroy: ->
      @set('confirmDelete', false)

    addItem: ->
      @sendAction('addItem', @get('block'))

Correct poorly named private method

Correct poorly named private method


if window.history?.pushState and window.history.replaceState
  document.addEventListener 'page:change', (event) =>

    # Google Analytics
    if window._gaq != undefined
      _gaq.push(['_trackPageview'])
    else if window.pageTracker != undefined
      pageTracker._trackPageview();

if window.history?.pushState and window.history.replaceState
  document.addEventListener 'page:change', (event) =>

    # Google Analytics
    if window.ga != undefined
      ga('set', 'location', location.href.split('#')[0])
      ga('send', 'pageview')
    else if window._gaq != undefined
      _gaq.push(['_trackPageview'])
    else if window.pageTracker != undefined
      pageTracker._trackPageview();

Add Google Universal Analytics support In order to send the correct 'location', I used ga('set', 'location') method.


logger = require 'winston'
Visualizer = require('../model/visualizer').Visualizer
authorisation = require './authorisation'
utils = require '../utils'

exports.getAllVisualizers = ->
  # Must be admin
  if not authorisation.inGroup 'admin', this.authenticated
    utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getAllVisualizers denied.", 'info'
    return

  try
    v = yield Visualizer.find().exec()
    this.body = v
  catch err
    utils.logAndSetResponse this, 500, "Could not fetch visualizers via the API: #{err}", 'error'

exports.removeVisualizer = (name) ->
  # Must be admin
  if not authorisation.inGroup 'admin', this.authenticated
    utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to removeVisualizer denied.", 'info'
    return

  name = unescape name

  try
    v = yield Visualizer.findOneAndRemove(name: name).exec()
    if not v
      return utils.logAndSetResponse this, 404, "Could not find visualizer with #{name}", 'info'

    this.body = "Successfully removed visualizer with name #{name}"
    logger.info "User #{this.authenticated.name} removed visualizer #{name}"
  catch e
    utils.logAndSetResponse this, 500, "Could not remove visualizer #{name} via the API #{e}", 'error'

logger = require 'winston'
Visualizer = require('../model/visualizer').Visualizer
authorisation = require './authorisation'
utils = require '../utils'

exports.getAllVisualizers = ->
  # Must be admin
  if not authorisation.inGroup 'admin', this.authenticated
    utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getAllVisualizers denied.", 'info'
    return

  try
    v = yield Visualizer.find().exec()
    this.body = v
  catch err
    utils.logAndSetResponse this, 500, "Could not fetch visualizers via the API: #{err}", 'error'

exports.removeVisualizer = (name) ->
  # Must be admin
  if not authorisation.inGroup 'admin', this.authenticated
    utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to removeVisualizer denied.", 'info'
    return

  name = unescape name

  try
    v = yield Visualizer.findOneAndRemove(name: name).exec()
    if not v
      return utils.logAndSetResponse this, 404, "Could not find visualizer with #{name}", 'info'

    this.body = "Successfully removed visualizer with name #{name}"
    logger.info "User #{this.authenticated.email} removed visualizer #{name}"
  catch e
    utils.logAndSetResponse this, 500, "Could not remove visualizer #{name} via the API #{e}", 'error'

Fix user name in log

Fix user name in log


class Workbench.Views.DatastreamShowView extends Backbone.View
  template: JST["workbench/templates/datastream"]

  tagName: "li"
  className: "datastream"

  initialize: ->
    @renderDeferred = $.Deferred()

    @chartView = new Workbench.Views.DatastreamChartView
      model: @model

    @latestView = new Workbench.Views.DatastreamLatestView
      model: @model

    @listenTo @model, "change:seriesData", =>
      # Defer until after main element is rendered
      @renderDeferred.done(=>
        # Render the chart
        _.delay(=>
          @chartView.setElement(@$(".chart")).render()
        , 0)

        # Render the latest view
        @latestView.setElement(@$("#latest_#{@model.id}")).render()
      )

  remove: ->
    @chartView.remove()
    @latestView.remove()
    super()

  render: ->
    @$el.animate("min-height": 200).promise().done(=>
      @$el.html(@template(@model.toJSON()))
      @renderDeferred.resolve()
    )
    this

class Workbench.Views.DatastreamShowView extends Backbone.Marionette.ItemView
  template: "workbench/templates/datastream"

  tagName: "li"
  className: "datastream"

  initialize: ->
    @renderDeferred = $.Deferred()

    @chartView = new Workbench.Views.DatastreamChartView
      model: @model

    @latestView = new Workbench.Views.DatastreamLatestView
      model: @model

    @listenTo @model, "change:seriesData", =>
      # Defer until after main element is rendered
      @renderDeferred.done(=>
        # Render the chart
        _.delay(=>
          @chartView.setElement(@$(".chart")).render()
        , 0)

        # Render the latest view
        @latestView.setElement(@$("#latest_#{@model.id}")).render()
      )

  onBeforeDestroy: ->
    @chartView.remove()
    @latestView.remove()

  onRender: ->
    @renderDeferred.resolve()

Update Datastream view to ItemView

Update Datastream view to ItemView


class @WalletPairingProgressDialogViewController extends DialogViewController

  view:
    contentContainer: "#content_container"

  onAfterRender: ->
    super
    # launch request
    @_request = @params.request
    @_request?.onComplete (screen, error) =>
      @_request = null
      if screen?
        dialog = new CommonDialogsMessageDialogViewController(kind: "success", title: t("wallet.pairing.errors.pairing_succeeded"), subtitle: _.str.sprintf(t("wallet.pairing.errors.dongle_is_now_paired"), screen.name))
      else
        dialog = new CommonDialogsMessageDialogViewController(kind: "error", title: t("wallet.pairing.errors.pairing_failed"), subtitle: t("wallet.pairing.errors." + error))
      dialog.show()
    @_request?.on 'finalizing', @_onFinalizing
    # show spinner
    @view.spinner = ledger.spinners.createLargeSpinner(@view.contentContainer[0])

  onDetach: ->
    super
    @_request?.off 'finalizing', @_onFinalizing

  onDismiss: ->
    super
    @_request?.cancel()

  _onFinalizing: ->
    ledger.m2fa.PairedSecureScreen.getScreensByUuidFromSyncedStore @_request.getDeviceUuid(), (screens, error) =>
      if screens?.length is 0
        @getDialog().push new WalletPairingFinalizingDialogViewController(request: @_request)
      else
        @_request.setSecureScreenName(screens[0].name)

class @WalletPairingProgressDialogViewController extends DialogViewController

  view:
    contentContainer: "#content_container"

  onAfterRender: ->
    super
    # launch request
    @_request = @params.request
    @_request?.onComplete (screen, error) =>
      @_request = null
      @dismiss () =>
        if screen?
          dialog = new CommonDialogsMessageDialogViewController(kind: "success", title: t("wallet.pairing.errors.pairing_succeeded"), subtitle: _.str.sprintf(t("wallet.pairing.errors.dongle_is_now_paired"), screen.name))
        else
          dialog = new CommonDialogsMessageDialogViewController(kind: "error", title: t("wallet.pairing.errors.pairing_failed"), subtitle: t("wallet.pairing.errors." + error))
        dialog.show()
    @_request?.on 'finalizing', @_onFinalizing
    # show spinner
    @view.spinner = ledger.spinners.createLargeSpinner(@view.contentContainer[0])

  onDetach: ->
    super
    @_request?.off 'finalizing', @_onFinalizing

  onDismiss: ->
    super
    @_request?.cancel()

  _onFinalizing: ->
    ledger.m2fa.PairedSecureScreen.getScreensByUuidFromSyncedStore @_request.getDeviceUuid(), (screens, error) =>
      if screens?.length is 0
        @getDialog().push new WalletPairingFinalizingDialogViewController(request: @_request)
      else
        @_request.setSecureScreenName(screens[0].name)

Fix "don't dismiss pairing progress dialog when displaying success"


angular.module("hyperadmin", [ "ui.router" ])
  .config ($httpProvider) ->
    authToken = $("meta[name=\"csrf-token\"]").attr("content")
    $httpProvider.defaults.headers.common["X-CSRF-TOKEN"] = authToken
    $httpProvider.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"
    $httpProvider.defaults.headers.common["Accept"] = "text/html,application/json"

Configure $http service for Rails

Configure $http service for Rails


kd                = require 'kd'
KDButtonView      = kd.ButtonView
KDProgressBarView = kd.ProgressBarView
KDCustomHTMLView  = kd.CustomHTMLView

module.exports = class ButtonViewWithProgressBar extends KDCustomHTMLView

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

    super options, data

    o               = @getOptions()     or {}
    buttonOptions   = o.buttonOptions   or {}
    progressOptions = o.progressOptions or {}
    loaderOptions   = o.loaderOptions   or {}

    buttonOptions.cb         = buttonOptions.callback
    buttonOptions.callback   = @bound 'handleCallback'
    progressOptions.cssClass = kd.utils.curry 'hidden', progressOptions.cssClass

    @addSubView @button      = new KDButtonView buttonOptions
    @addSubView @progressBar = new KDProgressBarView progressOptions

  handleCallback: ->

    buttonOptions = @getOption 'buttonOptions' or {}
    buttonOptions.cb?.call()

    @startProgress()

  startProgress: ->

    @button.disable()
    @progressBar.show()

  resetProgress: ->

    @show()
    @progressBar.hide()
    @updateProgress 0
    @button.enable()

  updateProgress: (value, unit, label) ->

    @progressBar.updateBar value, unit, label

kd                = require 'kd'
KDButtonView      = kd.ButtonView
KDProgressBarView = kd.ProgressBarView
KDCustomHTMLView  = kd.CustomHTMLView

module.exports = class ButtonViewWithProgressBar extends KDCustomHTMLView

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

    super options, data

    o                = @getOptions()     or {}
    o.startProgress ?= yes

    buttonOptions    = o.buttonOptions   or {}
    progressOptions  = o.progressOptions or {}
    loaderOptions    = o.loaderOptions   or {}

    buttonOptions.cb         = buttonOptions.callback
    buttonOptions.callback   = @bound 'handleCallback'
    progressOptions.cssClass = kd.utils.curry 'hidden', progressOptions.cssClass

    @addSubView @button      = new KDButtonView buttonOptions
    @addSubView @progressBar = new KDProgressBarView progressOptions

  handleCallback: ->

    buttonOptions = @getOption 'buttonOptions' or {}
    buttonOptions.cb?.call()

    @startProgress()  if @getOption 'startProgress'

  startProgress: ->

    @button.disable()
    @progressBar.show()

  resetProgress: ->

    @show()
    @progressBar.hide()
    @updateProgress 0
    @button.enable()

  updateProgress: (value, unit, label) ->

    @progressBar.updateBar value, unit, label

ButtonViewWithProgressBar: Add "startProgress" parameter to start to show progress bar.


# 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-text-editor[data-grammar~=fsharp]':
    'alt-enter': 'FSI:Send-Selection'
    'alt-/':'FSI:Send-Line'
    'ctrl-space': 'fsharp:autocomplete'
    'cmd-space': 'fsharp:autocomplete'

# 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 atom-text-editor:not([mini])[data-grammar~=fsharp]':
    'alt-enter': 'FSI:Send-Selection'
    'alt-/':'FSI:Send-Line'
    'ctrl-space': 'fsharp:autocomplete'
    'cmd-space': 'fsharp:autocomplete'

Change keymap selector (fixes alt-enter override in OSX)


ETahi.FigureOverlayView = ETahi.OverlayView.extend
  templateName: 'overlays/figure_overlay'
  layoutName: 'layouts/no_assignee_overlay_layout' #TODO: include assignee here?
  uploads: Ember.ArrayController.create content: []
  figures: null

  didInsertElement: ->
    @set 'figures', @get('controller.paper.figures')
    uploader = $('.js-jquery-fileupload')
    uploader.fileupload
      url: "/papers/#{@controller.get('paper.id')}/figures"
      dataType: 'json'
      method: 'POST'

    uploader.on 'fileuploadprocessalways', (e, data) =>
      file = data.files[0]
      @uploads.pushObject
        filename: file.name
        preview: file.preview?.toDataURL()
        progress: 0
        progressBarStyle: "width: 0%;"

    uploader.on 'fileuploadprogress', (e, data) =>
      currentUpload = @uploads.findBy('filename', data.files[0].name)
      progress = parseInt(data.loaded / data.total * 100.0, 10) #rounds the number
      Ember.setProperties currentUpload,
        progress: progress
        progressBarStyle: "width: #{progress}%;"

    uploader.on 'fileuploaddone', (e, data) =>
      newUpload = @uploads.findBy 'filename', data.files[0].name
      @uploads.removeObject newUpload

      store = @get('controller.store')
      updatedFigures = _.map data.result.figures, (figure) ->
        store.createRecord 'figure', figure

      @figures.pushObjects updatedFigures

ETahi.FigureOverlayView = ETahi.OverlayView.extend
  templateName: 'overlays/figure_overlay'
  layoutName: 'layouts/no_assignee_overlay_layout' #TODO: include assignee here?
  uploads: []
  figures: Em.computed.alias('controller.paper.figures')

  setupUpload: (->
    uploader = $('.js-jquery-fileupload')
    uploader.fileupload
      url: "/papers/#{@controller.get('paper.id')}/figures"
      dataType: 'json'
      method: 'POST'

    uploader.on 'fileuploadprocessalways', (e, data) =>
      file = data.files[0]
      @uploads.pushObject
        filename: file.name
        preview: file.preview?.toDataURL()
        progress: 0
        progressBarStyle: "width: 0%;"

    uploader.on 'fileuploadprogress', (e, data) =>
      currentUpload = @uploads.findBy('filename', data.files[0].name)
      progress = parseInt(data.loaded / data.total * 100.0, 10) #rounds the number
      Ember.setProperties currentUpload,
        progress: progress
        progressBarStyle: "width: #{progress}%;"

    uploader.on 'fileuploaddone', (e, data) =>
      newUpload = @uploads.findBy 'filename', data.files[0].name
      @uploads.removeObject newUpload

      store = @get('controller.store')
      updatedFigures = _.map data.result.figures, (figure) ->
        store.push 'figure', figure

      @get('figures').pushObjects updatedFigures

  ).on('didInsertElement')

Fix deleting of newly uploaded figures

Fix deleting of newly uploaded figures


IdrisController = require './idris-controller'
{CompositeDisposable} = require 'atom'

module.exports =
  active: false
  statusbar: null

  config:
    pathToIdris:
      type: 'string'
      default: 'idris'
      description: 'Path to the idris executable'

  activate: ->
    @disposables = new CompositeDisposable

    @active = @initIdris()
    if not @isActive()
      @disposables.add myself=atom.workspace.onDidOpen (event) =>
        item = event.item
        @active = @initIdris()
        if @isActive()
          myself.dispose()

  initIdris: () ->
    if @isActive()
      true
    else
      idrisFileOpened = atom.workspace.getTextEditors().some @isIdrisFile
      if idrisFileOpened
        @controller = new IdrisController
        if @statusbar
          @controller.attachStatusIndicator @statusbar
        subscription = atom.commands.add 'atom-text-editor[data-grammar~="idris"]', @controller.getCommands()
        @subscriptions = new CompositeDisposable
        @subscriptions.add subscription
        true
      else
        false

  isActive: ->
    @active

  isIdrisFile: (editor) ->
    editor.getGrammar?()?.scopeName == 'source.idris'

  deactivate: ->
    @subscriptions.dispose()
    this.controller.destroy()

  consumeStatusBar: (statusbar) ->
    @statusbar = statusbar
    @controller?.attachStatusIndicator statusbar

IdrisController = require './idris-controller'
{CompositeDisposable} = require 'atom'

module.exports =
  config:
    pathToIdris:
      type: 'string'
      default: 'idris'
      description: 'Path to the idris executable'

  activate: ->
    @controller = new IdrisController

    subscription = atom.commands.add 'atom-text-editor[data-grammar~="idris"]', @controller.getCommands()
    @subscriptions = new CompositeDisposable
    @subscriptions.add subscription

  deactivate: ->
    @subscriptions.dispose()
    this.controller.destroy()

  consumeStatusBar: (statusBar) ->
    @controller.attachStatusIndicator statusBar

Revert "only start the idris compiler when there is an idris file opened"

This reverts commit 89c0032aca1593cc851a65e7836ea8f539258a11.

Conflicts:
	lib/language-idris.coffee


"*":
  editor:
    fontFamily: "input"
    fontSize: 13
    showInvisibles: true
    showIndentGuide: true
    invisibles: {}
  core:
    themes: [
      "one-dark-ui"
      "facebook-syntax"
    ]
  welcome:
    showOnStartup: false
  whitespace:
    removeTrailingWhitespace: true
    ignoreWhitespaceOnCurrentLine: false
    ensureSingleTrailingNewline: true
  "exception-reporting":
    userId: "b6917109-8650-33d4-94c7-9edad015d73f"
  linter: {}
  "highlight-column": {}
  "block-cursor":
    primaryColor:
      red: 195
      green: 59
      blue: 50
      alpha: 1
"highlight-line":
  hideHighlightOnSelect: true

"*":
  editor:
    fontFamily: "input"
    fontSize: 12
    showInvisibles: true
    showIndentGuide: true
    invisibles: {}
  core:
    themes: [
      "atom-material-ui"
      "gotham-syntax"
    ]
  welcome:
    showOnStartup: false
  whitespace:
    removeTrailingWhitespace: true
    ignoreWhitespaceOnCurrentLine: false
    ensureSingleTrailingNewline: true
  "exception-reporting":
    userId: "b6917109-8650-33d4-94c7-9edad015d73f"
  linter: {}
  "highlight-column": {}
  "block-cursor":
    primaryColor:
      red: 195
      green: 59
      blue: 50
      alpha: 1
"highlight-line":
  hideHighlightOnSelect: true

Update atom theme and change font-size

Update atom theme and change font-size


fs = require 'fs'
optimist = require 'optimist'

parseOptions = (args=[]) ->
  options = optimist(args)
  options.usage('Usage: filings <command>')
  options.alias('h', 'help').describe('h', 'Print this usage message')
  options.alias('v', 'version').describe('v', 'Print the filings version')
  options

module.exports =
  run: (args=process.argv[2..]) ->
    options = parseOptions(args)
    if options.argv.v
      console.log JSON.parse(fs.readFileSync('package.json')).version
    else if options.argv.h
      options.showHelp()
    else if command = options.argv._.shift()
      console.error "Unrecognized command: #{command}"
    else
      options.showHelp()

fs = require 'fs'
path = require 'path'
optimist = require 'optimist'
{FormFour} = require './filings'

parseOptions = (args=[]) ->
  options = optimist(args)
  options.usage('Usage: filings <command>')
  options.alias('h', 'help').describe('h', 'Print this usage message')
  options.alias('v', 'version').describe('v', 'Print the filings version')
  options

module.exports =
  run: (args=process.argv[2..]) ->
    options = parseOptions(args)
    [command, commandArgs...] = options.argv._
    if options.argv.v
      console.log JSON.parse(fs.readFileSync('package.json')).version
    else if options.argv.h
      options.showHelp()
    else if command = options.argv._.shift()
      switch command
        when 'profit'
          reportPath = path.resolve(process.cwd(), commandArgs.shift())
          FormFour.open reportPath, (error, formFour) ->
            if error?
              console.error(error)
            else
              console.log(formFour.getProfit())
        else
          console.error "Unrecognized command: #{command}"
    else
      options.showHelp()

Add form 4 profit command

Add form 4 profit command


# Helper function needed to deal with array-like stylesheet objects.
toArray = (obj) -> Array::slice.call obj

# Scans your stylesheet for pseudo classes and adds a class with the same name.
# Thanks to Knyle Style Sheets for the idea.

processStyles = ->
  # Compile regular expression.
  pseudos = [ 'link', 'visited', 'hover', 'active', 'focus', 'target',
              'enabled', 'disabled', 'checked' ]
  pseudoRe = new RegExp(":((" + pseudos.join(")|(") + "))", "gi")

  # Only get inline style elements, and only the first one
  styleSheet = toArray(document.styleSheets).filter((ss) -> not ss.href?)[0]
  if styleSheet?
    processedStyles = toArray(styleSheet.cssRules).filter((rule) ->
      # Keep only rules with pseudo classes.
      rule.selectorText and rule.selectorText.match pseudoRe
    ).map((rule) ->
      # Replace : with . and encoded :
      rule.cssText.replace pseudoRe, ".\\3A $1"
    ).reduce((prev, cur) ->
      prev + cur
    )

    # Add the styles to the document
    styleEl = document.createElement "style"
    styleEl.appendChild document.createTextNode processedStyles
    document.getElementsByTagName("head")[0].appendChild styleEl

# Scans your stylesheet for pseudo classes and adds a class with the same name.
# Thanks to Knyle Style Sheets for the idea.
$ ->
  addPseudoClasses()

add = (a, b) -> a + b

addPseudoClasses = ->
  # Compile regular expression.
  pseudos = [ 'link', 'visited', 'hover', 'active', 'focus', 'target',
              'enabled', 'disabled', 'checked' ]
  pseudoRe = new RegExp(":((" + pseudos.join(")|(") + "))", "gi")

  processedPseudoClasses = _.toArray(document.styleSheets)
    # Filter out `link` elements.
    .filter(
      (ss) ->
        not ss.href?
    ).map((ss) ->
      _.toArray(ss.cssRules)
      .filter((rule) ->
        # Keep only rules with pseudo classes.
        rule.selectorText and rule.selectorText.match pseudoRe
      ).map((rule) ->
        # Replace : with . and encoded :
        rule.cssText.replace pseudoRe, ".\\3A $1"
      ).reduce(add)
    ).reduce(add, '')

  if processedPseudoClasses.length
    # Add a new style element with the processed pseudo class styles.
    $('head').append $('<style />').text(processedPseudoClasses)

Refactor pseudo class selector script a bit


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

  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth

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

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

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

  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth

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

# To check for scrollbars on the window,  use the slightly unusual window.innerHeight
# rather than  document.documentElement.clientHeight so it works in css compat mode.
window_has_scrollbar = -> window.innerHeight < document.documentElement.scrollHeight

saved_marginRight = null

FactlinkJailRoot.on 'modalOpened', ->
  if scrollbarWidth > 0 && window_has_scrollbar()
    console.log 'has scrollbar!'
    saved_marginRight = document.documentElement.style.marginRight
    document.documentElement.style.marginRight = scrollbarWidth + 'px'

  document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
FactlinkJailRoot.on 'modalClosed', ->
  if saved_marginRight != null
    document.documentElement.style.marginRight = saved_marginRight

  document.documentElement.removeAttribute('data-factlink-suppress-scrolling')

Change html margin when the sidebar opens to compensate for change in


angular.module('admin.orderCycles', ['ngTagsInput', 'admin.indexUtils', 'admin.enterprises'])
  .directive 'datetimepicker', ($timeout, $parse) ->
    require: "ngModel"
    link: (scope, element, attrs, ngModel) ->
      $timeout ->
        flatpickr(element,  Object.assign({},
                            window.FLATPICKR_DATETIME_DEFAULT, {
                            onOpen: (selectedDates, dateStr, instance) ->
                              instance.setDate(ngModel.$modelValue)
                              instance.input.dispatchEvent(new Event('focus', { bubbles: true }));
                            }));

  .directive 'ofnOnChange', ->
    (scope, element, attrs) ->
      element.bind 'change', ->
        scope.$apply(attrs.ofnOnChange)

  .directive 'ofnSyncDistributions', ->
    (scope, element, attrs) ->
      element.bind 'change', ->
        if !$(this).is(':checked')
          scope.$apply ->
            scope.removeDistributionOfVariant(attrs.ofnSyncDistributions)

angular.module('admin.orderCycles', ['ngTagsInput', 'admin.indexUtils', 'admin.enterprises'])
  .directive 'datetimepicker', ($timeout, $parse) ->
    require: "ngModel"
    link: (scope, element, attrs, ngModel) ->
      $timeout ->
        fp = flatpickr(element,  Object.assign({},
                            window.FLATPICKR_DATETIME_DEFAULT, {
                            onOpen: (selectedDates, dateStr, instance) ->
                              instance.setDate(ngModel.$modelValue)
                              instance.input.dispatchEvent(new Event('focus', { bubbles: true }));
                            }));
        fp.minuteElement.addEventListener "keyup", (e) ->
          if !isNaN(event.target.value)
            fp.setDate(fp.selectedDates[0].setMinutes(e.target.value), true)
        fp.hourElement.addEventListener "keyup", (e) ->
          if !isNaN(event.target.value)
            fp.setDate(fp.selectedDates[0].setHours(e.target.value), true)

  .directive 'ofnOnChange', ->
    (scope, element, attrs) ->
      element.bind 'change', ->
        scope.$apply(attrs.ofnOnChange)

  .directive 'ofnSyncDistributions', ->
    (scope, element, attrs) ->
      element.bind 'change', ->
        if !$(this).is(':checked')
          scope.$apply ->
            scope.removeDistributionOfVariant(attrs.ofnSyncDistributions)

Add keyup event listener on hour/minute inputs

 - To set the date (and trigger the onChange event) when the user is typing through its keyboard


UIWatcher = require './ui-watcher'

module.exports =
  activate: (state) ->
    uiWatcher = new UIWatcher
      themeManager: atom.themes

    rootView.command 'dev-live-reload:reload-all', ->
      uiWatcher.reloadAll()

$ = require 'jquery'
UIWatcher = require './ui-watcher'

module.exports =
  activate: (state) ->

    # HACK: I need an actvation event when the ui or packages are all loaded.
    # It cant watch all the packages until they are all loaded.
    createUIWatcher = ->
      uiWatcher = new UIWatcher
        themeManager: atom.themes
      $(window).off 'focus', createUIWatcher
    $(window).on 'focus', createUIWatcher

    rootView.command 'dev-live-reload:reload-all', ->
      uiWatcher.reloadAll()

Create the UIWatcher after everything has been loaded


#= require trix/controllers/abstract_input_controller

class Trix.Level2InputController extends Trix.AbstractInputController
  events:
    beforeinput: (event) ->
      log(event)

    input: (event) ->
      log(event)

log = (event) ->
  console.log("[#{event.type}] #{event.inputType}: #{JSON.stringify(event.data)}")

#= require trix/controllers/abstract_input_controller

{objectsAreEqual} = Trix

class Trix.Level2InputController extends Trix.AbstractInputController
  mutationIsExpected: (mutationSummary) ->
    result = objectsAreEqual(mutationSummary, @inputSummary)
    console.log("[mutation] [#{if result then "expected" else "unexpected"}] #{JSON.stringify({mutationSummary})}")
    delete @inputSummary
    result

  events:
    beforeinput: (event) ->
      @inputSummary = @[event.inputType]?(event)
      console.group(event.inputType)
      console.log("[#{event.type}] #{JSON.stringify(event.data)} #{JSON.stringify({@inputSummary})}")

    input: (event) ->
      console.log("[#{event.type}] #{JSON.stringify(event.data)}")
      Promise.resolve().then(console.groupEnd)

  insertText: (event) ->
    textAdded = event.data
    @delegate?.inputControllerWillPerformTyping()
    @responder?.insertString(textAdded)
    {textAdded}

  deleteContentBackward: (event) ->
    textDeleted = [event.getTargetRanges()...].map(staticRangeToRange).join("")
    @delegate?.inputControllerWillPerformTyping()
    @responder?.deleteInDirection("backward")
    {textDeleted}

staticRangeToRange = (staticRange) ->
  range = document.createRange()
  range.setStart(staticRange.startContainer, staticRange.startOffset)
  range.setEnd(staticRange.endContainer, staticRange.endOffset)
  range

Add "insertText" and "deleteContentBackward" inputType handlers


determineBrowser = ->
  [ 'chrome', 'firefox', 'safari', 'phantom_js', 'unsupported-browser' ]
  .filter( (browser) -> $('html.'+browser).length
  )[0]

extension_link_by_browser =
  firefox: 'https://static.factlink.com/extension/firefox/factlink-latest.xpi'
  safari: 'https://static.factlink.com/extension/firefox/factlink.safariextz'
  chrome: 'javascript:chrome.webstore.install()'

icon_class_by_browser =
  firefox: 'install-firefox'
  safari: 'install-safari'
  chrome: 'install-chrome'

window.ReactInstallExtensionButton = React.createClass
  displayName: 'ReactInstallExtensionButton'
  render: ->
    browserName = determineBrowser()
    extension_link = extension_link_by_browser[browserName]
    icon_class = icon_class_by_browser[browserName]
    extra_class = if @props.huge_button then 'button-huge' else null
    if document.documentElement.getAttribute('data-factlink-extension-loaded') != undefined
      _button ['button', extra_class,
        disabled: true],
        'Factlink already installed.'
    else
      _div [],
        _div ['visible-when-chrome'],
          _a ['button-success', extra_class, href: extension_link],
            _span [icon_class]
            'Install Factlink for ' + browserName.capitalize()
        _div ['visible-when-firefox'],
          _a ['button-success', extra_class, href: extension_link],
            _span [icon_class]
            'Install Factlink for ' + browserName.capitalize()
        _div ['visible-when-safari'],
          _a ['button-success', extra_class, href: extension_link],
            _span [icon_class]
            'Install Factlink for ' + browserName.capitalize()

determineBrowser = ->
  [ 'chrome', 'firefox', 'safari', 'phantom_js', 'unsupported-browser' ]
  .filter( (browser) -> $('html.'+browser).length
  )[0]

extension_link_by_browser =
  firefox: 'https://static.factlink.com/extension/firefox/factlink-latest.xpi'
  safari: 'https://static.factlink.com/extension/firefox/factlink.safariextz'
  chrome: 'javascript:chrome.webstore.install()'

icon_class_by_browser =
  firefox: 'install-firefox'
  safari: 'install-safari'
  chrome: 'install-chrome'

window.ReactInstallExtensionButton = React.createClass
  displayName: 'ReactInstallExtensionButton'
  render: ->
    browserName = determineBrowser()
    extension_link = extension_link_by_browser[browserName]
    icon_class = icon_class_by_browser[browserName]
    extra_class = if @props.huge_button then 'button-huge' else null
    if document.documentElement.getAttribute('data-factlink-extension-loaded') != undefined
      _button ['button', extra_class,
        disabled: true],
        'Factlink already installed.'
    else
      _a ['button-success', extra_class, href: extension_link],
        _span [icon_class]
        'Install Factlink for ' + browserName.capitalize()

Simplify browser-specific extension install logic


# IMPORTANT: choose one
RL_LIB = "libreadline"  # NOTE: libreadline is GPL
#RL_LIB = "libedit"

HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history')

rlwrap = {} # namespace for this module in web context

ffi = require('ffi')
fs = require('fs')

rllib = ffi.Library(RL_LIB, {
    'readline': ['string', ['string']],
    'add_history': ['int', ['string']]})

rl_history_loaded = false

exports.readline = rlwrap.readline = (prompt = 'user> ') ->
  if !rl_history_loaded
    rl_history_loaded = true
    lines = []
    if fs.existsSync(HISTORY_FILE)
        lines = fs.readFileSync(HISTORY_FILE).toString().split("\n");

    # Max of 2000 lines
    lines = lines[Math.max(lines.length - 2000, 0)..]
    rllib.add_history(line) for line in lines when line != ""

  line = rllib.readline prompt
  if line
    rllib.add_history line
    try
      fs.appendFileSync HISTORY_FILE, line + "\n"
    catch exc
      true

  line

# vim: ts=2:sw=2

readlineSync = require('readline-sync');

exports.readline = (prompt = 'user> ') ->
  readlineSync.question(prompt)

Fix CoffeeScript example. Use readline-sync. Old ffi thing does not work.


React = require 'react'
deep = require 'deep-get-set'

module.exports = React.createClass
  displayName: "SimpleTable"

  propTypes:
    columns: React.PropTypes.array
    data: React.PropTypes.array

  render: ->
    columns = @props.columns.map (column) ->
      if typeof column is "string"
        {column}
      else
        {column.displayName}

    body = @props.data.map (rowData, i) =>
      row = []
      for column in @props.columns
        # Columns can either be a simple string or be an object that defines
        # both a displayName and path for accessing the data.
        if typeof column is "string"
          datum = deep(rowData, column.toLowerCase())
          key = i + "-" + column
        else if column.path?
          datum = deep(rowData, column.path)
          key = i + "-" + column.path
        else if column.function?
          datum = column.function(rowData)
          key = i + "-" + column.displayName
        row.push {datum}
      return
{row}

    return (

{columns}

{body}

    )

Make react happy with keys

Make react happy with keys


class FactlinkJailRoot.ControlIframe
  constructor: ->
    @el = document.createElement('iframe')
    @el.className = 'factlink-control-frame'
    #need to append to outer document before we can access frame document.
    FactlinkJailRoot.$factlinkCoreContainer.append(@el)
    @$el = $(@el)
    @doc = @el.contentWindow.document;
    @doc.open()
    #need doctype to avoid quirks mode
    @doc.write('<!DOCTYPE html><title></title>')
    @doc.close()
    style = @doc.createElement('style')
    style.appendChild(@doc.createTextNode(FrameCss))
    @doc.head.appendChild(style)

  setContent: (contentNode) ->
    bodyEl = @doc.body
    while bodyEl.firstChild
      bodyEl.removeChild(bodyEl.firstChild)
    bodyEl.appendChild(contentNode)
    @resizeFrame()

  resizeFrame: ->
    @el.style.width = @doc.body.clientWidth  + 'px'
    @el.style.height = @doc.body.clientHeight + 'px'

  destroy: ->
    return unless @el
    @doc = null
    @el.parentElement?.removeChild(@el)
    @$el = @el = null

control_visibility_transition_time = 300+1000/60 #keep in sync with scss

class FactlinkJailRoot.ControlIframe
  constructor: ->
    @el = document.createElement('iframe')
    @el.className = 'factlink-control-frame'
    #need to append to outer document before we can access frame document.
    FactlinkJailRoot.$factlinkCoreContainer.append(@el)
    @$el = $(@el)
    @doc = @el.contentWindow.document;
    @doc.open()
    #need doctype to avoid quirks mode
    @doc.write('<!DOCTYPE html><title></title>')
    @doc.close()
    style = @doc.createElement('style')
    style.appendChild(@doc.createTextNode(FrameCss))
    @doc.head.appendChild(style)

  setContent: (contentNode) ->
    bodyEl = @doc.body
    while bodyEl.firstChild
      bodyEl.removeChild(bodyEl.firstChild)
    bodyEl.appendChild(contentNode)
    @resizeFrame()

  resizeFrame: ->
    @el.style.width = @doc.body.clientWidth  + 'px'
    @el.style.height = @doc.body.clientHeight + 'px'

  fadeIn: ->
    @$el.addClass 'factlink-control-visible'
    FactlinkJailRoot.Timer control_visibility_transition_time

  fadeOut: ->
    @$el.removeClass 'factlink-control-visible'
    FactlinkJailRoot.Timer control_visibility_transition_time

  #feature:should we communicate visibility to the control contents?

  destroy: ->
    return unless @el
    @doc = null
    @el.parentElement?.removeChild(@el)
    @$el = @el = null

Implement fadeout/in in control iframe; return deferred for completion handlers