class Views.DiscussionView extends View
  @content: ->
    @div class: 'discussion', =>
      @subview 'commentsList', new Views.RelationView(
        buildItem: (comment) -> new Views.CommentItem(comment)
      )
      @div class: 'text-entry', =>
        @textarea rows: 2, outlet: 'textarea'
        @button "Submit Comment", class: 'btn pull-right', click: 'createComment'

  autoScroll: true

  initialize: (@comments) ->
    @commentsList.scroll => @assignAutoscroll()
    @commentsList.onInsert = => @scrollToBottom() if @autoScroll
    @textarea.keydown (e) =>
      if e.keyCode == 13 && !e.ctrlKey # enter (not ctrl-enter though)
        e.preventDefault()
        @createComment()

  setComments: (@comments) ->
    @commentsList.setRelation(@comments)

  afterAttach: ->
    @scrollToBottom()

  createComment: ->
    body = @textarea.val()
    if /\S/.test(body)
      @textarea.val('')
      @comments.create({body})

  scrollToBottom: ->
    @commentsList.scrollTop(@maxScroll())

  maxScroll: ->
    @commentsList.scrollTop() + @commentsList.outerHeight()

  assignAutoscroll: ->
    @autoScroll = (@commentsList.prop('scrollHeight') == @maxScroll())

  remove: (selector, keepData) ->
    unless keepData
      @commentsList.remove()

class Views.DiscussionView extends View
  @content: ->
    @div class: 'discussion', =>
      @subview 'commentsList', new Views.RelationView(
        buildItem: (comment) -> new Views.CommentItem(comment)
      )
      @div class: 'text-entry', =>
        @textarea rows: 2, outlet: 'textarea'
        @button "Submit Comment", class: 'btn pull-right', click: 'createComment'

  autoScroll: true

  initialize: (@comments) ->
    @commentsList.scroll => @assignAutoscroll()
    @commentsList.onInsert = => @scrollToBottom() if @autoScroll
    @textarea.keydown (e) =>
      if e.keyCode == 13 && (e.metaKey || e.ctrlKey)
        e.preventDefault()
        @createComment()

  setComments: (@comments) ->
    @commentsList.setRelation(@comments)

  afterAttach: ->
    @scrollToBottom()

  createComment: ->
    body = @textarea.val()
    if /\S/.test(body)
      @textarea.val('')
      @comments.create({body})

  scrollToBottom: ->
    @commentsList.scrollTop(@maxScroll())

  maxScroll: ->
    @commentsList.scrollTop() + @commentsList.outerHeight()

  assignAutoscroll: ->
    @autoScroll = (@commentsList.prop('scrollHeight') == @maxScroll())

  remove: (selector, keepData) ->
    unless keepData
      @commentsList.remove()

Allow newlines in comment field. Meta/ctrl-enter submits


# Description:
#   A cow's gonna be a cow.
#
# Commands:
#   hubot moo* - Reply w/ moo

module.exports = (robot) ->
  robot.hear /\bmo{2,}\b/i, (msg) ->
    if msg.envelope.room == "1s_and_0s"
      robot.messageHipchat "MOOOOOOOOOOOOOOOOO"
    else
      if !msg.envelope.room
        msg.send "This attempt has been logged"

# Description:
#   A cow's gonna be a cow.
#
# Commands:
#   hubot moo* - Reply w/ moo

module.exports = (robot) ->
  robot.hear /\bmo{2,}\b/i, (msg) ->
    if msg.envelope.room == "1s_and_0s"
      robot.messageHipchat "MOOOOOOOOOOOOOOOOO"
    else
      if !msg.envelope.room
        msg.send "This incident will be reported"

Change "This attempt has been logged" to something more familiar

Test Plan: None

Reviewers: ben

Reviewed By: ben

Differential Revision: http://phabricator.khanacademy.org/D3700


# This is a manifest file that'll be compiled into application.js, which will include all the files
# listed below.
#
# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
#
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
# GO AFTER THE REQUIRES BELOW.
#
#= require jquery
#= require jquery_ujs
#= require_tree .

Remove require tree to eliminate unneeded scripts


{Wrappers} = require "#{__dirname}/wrappers"

class BeanBuilder

  constructor: ({@basePath, @suffix}) ->
    if @suffix[0] is '_'
      @suffix = @suffix[1..]

  build: (config) ->
    throw new Error 'No task defined.' unless config
    suffixLength = @suffix.length
    config.type = Wrappers.underscored(config.type ? 'default')
    config.type = "#{config.type}_#{@suffix}" unless config.type[-suffixLength..] is "_#{@suffix}"
    className = Wrappers.classify config.type
    filePath = "#{@basePath}/#{config.type}"
    requiredScope = require filePath
    Clazz = requiredScope[className]
    throw new Error "Type #{className} was not found in file #{filePath}." unless Clazz
    new Clazz config, @

exports.BeanBuilder = BeanBuilder

{Wrappers} = require "#{__dirname}/wrappers"

class BeanBuilder

  basePath: null
  suffix: null
  createObjectCallback: null

  constructor: ({@basePath, @suffix, @createObjectCallback}) ->
    if @suffix[0] is '_'
      @suffix = @suffix[1..]

  build: (config) ->
    throw new Error 'No task defined.' unless config
    suffixLength = @suffix.length
    config.type = Wrappers.underscored(config.type ? 'default')
    config.type = "#{config.type}_#{@suffix}" unless config.type[-suffixLength..] is "_#{@suffix}"
    className = Wrappers.classify config.type
    filePath = "#{@basePath}/#{config.type}"
    requiredScope = require filePath
    Clazz = requiredScope[className]
    throw new Error "Type #{className} was not found in file #{filePath}." unless Clazz
    if typeof @createObjectCallback is 'function'
      @createObjectCallback Clazz, config
    else
      new Clazz

exports.BeanBuilder = BeanBuilder

Make BeanBuilder more flexible, fix members' scopes.


SideView = require '../lib/side-view'

describe 'SideView', ->

  side = null
  view = null
  line = null

  beforeEach ->
    line = {}
    side = {
      klass: -> 'klass',
      site: -> 99,
      description: -> '',
      lines: -> [line]
    }
    view = new SideView(side)

  it 'triggers conflict resolution', ->
    side.resolve = -> null
    spyOn(side, "resolve")

    view.useMe()

    expect(side.resolve).toHaveBeenCalled()

  describe 'when chosen as the resolution', ->

    beforeEach ->
      side.wasChosen = -> true
      line.addClass = -> null

    it 'adds the "resolved" class'
    it 'deletes the marker line'

  describe 'when not chosen as the resolution', ->

    beforeEach ->
      side.wasChosen = -> true

    it 'deletes its hunks lines'
    it ''

SideView = require '../lib/side-view'

Conflict = require '../lib/conflict'
util = require './util'

describe 'SideView', ->
  [view, ours, theirs] = []

  beforeEach ->
    editor = util.openPath("single-2way-diff.txt").getEditor()
    conflict = Conflict.all(editor)[0]
    [ours, theirs] = [conflict.ours, conflict.theirs]
    view = new SideView(ours)

  it 'triggers conflict resolution', ->
    spyOn(ours, "resolve")
    view.useMe()
    expect(ours.resolve).toHaveBeenCalled()

  describe 'when chosen as the resolution', ->

    beforeEach ->
      ours.resolve()

    it 'adds the "resolved" class'
    it 'deletes the marker line'

  describe 'when not chosen as the resolution', ->

    beforeEach ->
      theirs.resolve()

    it 'deletes its hunks lines'
    it ''

Use a real editor in SideViewSpec.

Use a real editor in SideViewSpec.


Settings = require "settings-sharelatex"
logger = require "logger-sharelatex"
logger.initialize("track-changes")

HttpController = require "./app/js/HttpController"
express = require "express"
app = express()

app.use express.logger()

app.post "/doc/:doc_id/flush", HttpController.flushUpdatesWithLock

app.use (error, req, res, next) ->
	logger.error err: error, "an internal error occured"
	res.send 500

port = Settings.internal?.trackchanges?.port or 3014
host = Settings.internal?.trackchanges?.host or "localhost"
app.listen port, host, (error) ->
	if error?
		logger.error err: error, "could not start track-changes server"
	else
		logger.log "track changes api listening on http://#{host}:#{port}"

Settings = require "settings-sharelatex"
logger = require "logger-sharelatex"
logger.initialize("track-changes")

HttpController = require "./app/js/HttpController"
express = require "express"
app = express()

app.use express.logger()

app.post "/doc/:doc_id/flush", HttpController.flushUpdatesWithLock

app.get "/status", (req, res, next) ->
	res.send "track-changes is alive"

app.use (error, req, res, next) ->
	logger.error err: error, "an internal error occured"
	res.send 500

port = Settings.internal?.trackchanges?.port or 3014
host = Settings.internal?.trackchanges?.host or "localhost"
app.listen port, host, (error) ->
	if error?
		logger.error err: error, "could not start track-changes server"
	else
		logger.log "track changes api listening on http://#{host}:#{port}"

Add in /status end point

Add in /status end point


fs = require 'fs'
path = require 'path'

module.exports =
    selector: '.source.lua'
    disableForSelector: '.source.lua .comment, .source.lua .string'

    inclusionPriority: 10
    excludeLowerPriority: true

    # Required: Return a promise, an array of suggestions, or null.
    getSuggestions: ( { editor, bufferPosition, scopeDescriptor, prefix, activatedManually } ) ->
        suggestions = @findSuggestions( @completions, prefix )
        suggestions

    findSuggestions: ( completions, prefix ) ->
        suggestions = []
        for item in completions
            if @compareStrings( item.displayText, prefix )
                suggestions.push( {
                        displayText: item.displayText
                        snippet: item.snippet
                        type: item.type
                    } )

        suggestions

    loadCompletions: ->
        @completions = {}
        fs.readFile path.resolve( __dirname, '..', './snippets/love-completions.json' ), ( error, data ) =>
            @completions = JSON.parse( data ) unless error?
            return

    compareStrings: ( a, b ) ->
        a[0].toLowerCase() is b[0].toLowerCase()

fs = require 'fs'
path = require 'path'

module.exports =
    selector: '.source.lua'
    disableForSelector: '.source.lua .comment, .source.lua .string'

    inclusionPriority: 10
    excludeLowerPriority: true

    # Required: Return a promise, an array of suggestions, or null.
    getSuggestions: ( { editor, bufferPosition, scopeDescriptor, prefix, activatedManually } ) ->
        if prefix.length is 0
            return []

        suggestions = @findSuggestions( @completions, prefix )
        suggestions

    findSuggestions: ( completions, prefix ) ->
        suggestions = []
        for item in completions
            if @compareStrings( item.displayText, prefix )
                suggestions.push( {
                        displayText: item.displayText
                        snippet: item.snippet
                        type: item.type
                    } )

        suggestions

    loadCompletions: ->
        @completions = {}
        fs.readFile path.resolve( __dirname, '..', './snippets/love-completions.json' ), ( error, data ) =>
            @completions = JSON.parse( data ) unless error?
            return

    compareStrings: ( a, b ) ->
        a[0].toLowerCase() is b[0].toLowerCase()

Return empty array when prefix is empty

Return empty array when prefix is empty

Prevents an error when the users types an invalid character (e.g. german
umlauts).


$ ->
  if $('#setting_show_map').length
    $this = $('#setting_show_map')

    if $this.is(':checked')
    else
      $('.map-settings').hide()

    $this.on 'click', (e) ->
      if $this.is(':checked')
        $('.map-settings').slideDown()
      else
        $('.map-settings').slideUp()

$ ->
  if $('#setting_show_map').length
    $this = $('#setting_show_map')

    unless $this.is(':checked')
      $('.map-settings').hide()

    $this.on 'click', (e) ->
      if $this.is(':checked')
        $('.map-settings').slideDown()
      else
        $('.map-settings').slideUp()

Improve coffeescript using unless attribute


show_tab = () ->
  if location.hash.match(/^#tab-/)
    $(".nav-tabs a[href=##{location.hash.replace('#tab-', '')}]").tab('show')

if $('.nav-tabs').length > 0
  default_hash = $('.nav-tabs .active a')[0].hash.replace('#', '')
  window.history.replaceState({}, '', ("#tab-" + default_hash))

show_tab()

$(window).on 'popstate', (e)->
    show_tab()

$('a[data-toggle="tab"]').on 'shown.bs.tab', (e) ->
  window.location.hash = e.target.hash.replace('#', '#tab-')

if errored_pane = $('.tab-pane .field_with_errors:eq(1)').parents('.tab-pane').attr('id')
  $(".nav-tabs a[href=##{errored_pane}]").tab('show')

show_tab = ->
  if location.hash.match(/^#tab-/)
    $(".nav-tabs a[href=##{location.hash.replace('#tab-', '')}]").tab('show')

show_default_tab = ->
  if $('.nav-tabs').length > 0
    default_hash = $('.nav-tabs .active a')[0].hash.replace('#', '')
    window.history.replaceState({}, '', ("#tab-" + default_hash))

show_tab() || show_default_tab()

$(window).on 'popstate', show_tab

$('a[data-toggle="tab"]').on 'shown.bs.tab', (e) ->
  window.location.hash = e.target.hash.replace('#', '#tab-')

if errored_pane = $('.tab-pane .field_with_errors:eq(1)').parents('.tab-pane').attr('id')
  $(".nav-tabs a[href=##{errored_pane}]").tab('show')

Fix bug showing tab when coming from another page


define [
	"base"
], (App) ->
	App.controller "HistoryV2DiffController", ($scope, ide, event_tracking) ->
		$scope.restoreState =
			inflight: false
			error: false

		$scope.restoreDeletedFile = () ->
			pathname = $scope.history.selection.pathname
			return if !pathname?
			version = $scope.history.selection.docs[pathname]?.deletedAtV
			return if !version?
			event_tracking.sendMB "history-v2-restore-deleted"
			$scope.restoreState.inflight = true
			ide.historyManager
				.restoreFile(version, pathname)
				.then (response) ->
					{ data } = response
					openEntity(data)
				.catch () ->
					ide.showGenericMessageModal('Sorry, something went wrong with the restore')
				.finally () ->
					$scope.restoreState.inflight = false

		openEntity = (data) ->
			iterations = 0
			{id, type} = data
			do tryOpen = () ->
				if iterations > 5
					return
				entity = ide.fileTreeManager.findEntityById(id)
				if entity? and type == 'doc'
					ide.editorManager.openDoc(entity)
				if entity? and type == 'file'
					ide.binaryFilesManager.openFile(entity)
				else
					setTimeout(tryOpen, 500)

define [
	"base"
], (App) ->
	App.controller "HistoryV2DiffController", ($scope, ide, event_tracking) ->
		$scope.restoreState =
			inflight: false
			error: false

		$scope.restoreDeletedFile = () ->
			pathname = $scope.history.selection.pathname
			return if !pathname?
			version = $scope.history.selection.docs[pathname]?.deletedAtV
			return if !version?
			event_tracking.sendMB "history-v2-restore-deleted"
			$scope.restoreState.inflight = true
			ide.historyManager
				.restoreFile(version, pathname)
				.then (response) ->
					{ data } = response
					openEntity(data)
				.catch () ->
					ide.showGenericMessageModal('Sorry, something went wrong with the restore')
				.finally () ->
					$scope.restoreState.inflight = false

		openEntity = (data) ->
			iterations = 0
			{id, type} = data
			do tryOpen = () ->
				if iterations > 5
					return
				iterations += 1
				entity = ide.fileTreeManager.findEntityById(id)
				if entity? and type == 'doc'
					ide.editorManager.openDoc(entity)
				else if entity? and type == 'file'
					ide.binaryFilesManager.openFile(entity)
				else
					setTimeout(tryOpen, 500)

Fix bad if statement sending doc open into infinite loop


define (require) ->
  $ = require('jquery')
  Backbone = require('backbone')
  router = require('cs!router')
  analytics = require('cs!helpers/handlers/analytics') # Setup Analytics Handler

  # The root URI prefixed on all non-external AJAX and Backbone URIs
  root = '/'

  init = (options = {}) ->
    # Append /test to the root if the app is in test mode
    if options.test
      root += 'test/'

    external = new RegExp('^((f|ht)tps?:)?//')

    # Catch internal application links and let Backbone handle the routing
    $(document).on 'click', 'a:not([data-bypass])', (e) ->
      href = $(this).attr('href')

      # Only handle links intended to be processed by Backbone
      if e.isDefaultPrevented() or href.charAt(0) is '#' or
        (href.length > 9 and href.substring(0,7) is 'mailto:') then return

      e.preventDefault()

      if external.test(href)
        window.open(href, '_blank')
      else
        router.navigate(href, {trigger: true})

    Backbone.history.start
      pushState: true
      root: root

    # Prefix all non-external AJAX requests with the root URI
    $.ajaxPrefilter (options, originalOptions, jqXHR) ->
      if not external.test(options.url)
        options.url = root + options.url

      return

  return {init: init}

define (require) ->
  $ = require('jquery')
  Backbone = require('backbone')
  router = require('cs!router')
  analytics = require('cs!helpers/handlers/analytics') # Setup Analytics Handler

  # The root URI prefixed on all non-external AJAX and Backbone URIs
  root = '/'

  init = (options = {}) ->
    # Append /test to the root if the app is in test mode
    if options.test
      root += 'test/'

    external = new RegExp('^((f|ht)tps?:)?//')

    # Catch internal application links and let Backbone handle the routing
    $(document).on 'click', 'a:not([data-bypass])', (e) ->
      href = $(this).attr('href')

      # Only handle links intended to be processed by Backbone
      if e.isDefaultPrevented() or href.charAt(0) is '#' or /^mailto:.+/.test(link) then return

      e.preventDefault()

      if external.test(href)
        window.open(href, '_blank')
      else
        router.navigate(href, {trigger: true})

    Backbone.history.start
      pushState: true
      root: root

    # Prefix all non-external AJAX requests with the root URI
    $.ajaxPrefilter (options, originalOptions, jqXHR) ->
      if not external.test(options.url)
        options.url = root + options.url

      return

  return {init: init}

Use regex to check for 'mailto:' link

Use regex to check for 'mailto:' link


g = module.parent.exports

g.task "watch", ->
  g.s.listen 35729, (err) ->
    return console.log(err) if err

    g.watch "bower.json", ["vendor"]
    g.watch ["fonts/**/*", "web/vendor/**/fonts/*"], ["fonts"]
    g.watch "images/**/*", ["images"]
    g.watch "scripts/**/*.coffee", ["scripts"]
    g.watch "styles/**/*.scss", ["styles"]
    g.watch "{app,config,views}/**/*", ["views"]
    g.watch "styles/_variables.scss", ["vendor-css"]

Recompile vendor css on bootstrap var changes


angular.module('doubtfire.units.states.tasks.viewer.directives.task-sheet-view', [])

.directive('taskSheetView', ->
  restrict: 'E'
  templateUrl: 'units/states/tasks/viewer/directives/task-sheet-view/task-sheet-view.tpl.html'
  scope:
    task: '='
    unit: '='
  controller: ($scope, $timeout, TaskFeedback, taskService, alertService, listenerService, Task, Unit) ->
    # Cleanup
    listeners = listenerService.listenTo($scope)

    $scope.clearSelectedTask = -> $scope.task = null

    listeners.push $scope.$watch 'task', (newTask) ->
      setDetails = ->
        if newTask?
          $scope.hasPdf = newTask.has_task_pdf
          $scope.taskPdfUrl = Task.getTaskPDFUrl($scope.unit, $scope.task)
          console.log($scope.taskPdfUrl)
      setDetails()
)

angular.module('doubtfire.units.states.tasks.viewer.directives.task-sheet-view', [])

.directive('taskSheetView', ->
  restrict: 'E'
  templateUrl: 'units/states/tasks/viewer/directives/task-sheet-view/task-sheet-view.tpl.html'
  scope:
    task: '='
    unit: '='
  controller: ($scope, $timeout, TaskFeedback, taskService, alertService, listenerService, Task, Unit) ->
    # Cleanup
    listeners = listenerService.listenTo($scope)

    $scope.clearSelectedTask = -> $scope.task = null

    listeners.push $scope.$watch 'task', (newTask) ->
      setDetails = ->
        if newTask?
          $scope.hasPdf = newTask.has_task_pdf
          $scope.taskPdfUrl = Task.getTaskPDFUrl($scope.unit, $scope.task)
      setDetails()
)

Remove log to task pdf url

FIX: Remove log to task pdf url


$(".paypal-status").html "<%= escape_javascript(render :partial => 'devise/registrations/verified_status', :formats => [ :html ]) %>"
$(".verified-status-tag").html "<%= escape_javascript(render :partial => 'shared/verified_status_tag', :formats => [ :html ]) %>"
$(".modal").removeClass "paypal-confirm-modal" if $(".modal").hasClass("paypal-confirm-modal")
$(".modal").modal "hide"

$(".account-status-paypal").html "<%= escape_javascript(render :partial => 'devise/registrations/verified_status', :formats => [ :html ]) %>"
$(".account-status-linked").html "<%= escape_javascript(render :partial => 'users/club_live_status', :formats => [ :html ]) %>"
$(".verified-status-tag").html "<%= escape_javascript(render :partial => 'shared/verified_status_tag', :formats => [ :html ]) %>"
$(".modal").removeClass "paypal-confirm-modal" if $(".modal").hasClass("paypal-confirm-modal")
$(".modal").modal "hide"

Update the Account PayPal Response for New View

Update the Account view PayPal verification response to update the new
DOM elements.


_ = require 'lodash'

module.exports = (options) ->
    (req, res, next) ->
        if not _.isArray options
            options = [options]

        options.push 'OPTIONS'

        res.header 'Access-Control-Allow-Methods', options.join ", "
        res.header 'Access-Control-Allow-Headers', 'Content-Type'
        res.header 'Access-Control-Max-Age', 0

        res.status 204
        .end()

_ = require 'lodash'

module.exports = (options) ->
    if not _.isArray options
        options = [options]
    options.push 'OPTIONS'

    (req, res, next) ->
        res.header 'Access-Control-Allow-Methods', options.join ", "
        res.header 'Access-Control-Allow-Headers', 'Content-Type'
        res.header 'Access-Control-Max-Age', 0

        res.status 204
        .end()

Fix OPTIONS accumulation on OPTIONS requests


App.CwbResultTableController = Em.Controller.extend

  needs: ['resultTable']

  # We get the actual page of results from the resultTableController; this
  # controller is only responsible for doing CWB-specific formatting of
  # those results
  resultPageBinding: 'controllers.resultTable.content'

  arrangedContent: (->
    resultPage = @get('resultPage')

    if resultPage
      resultPage.map (row) ->
        m = row.match(/<s_id (.+)>:\s+(.+)<(.+?)>(.+)/)
        sId:       m[1]
        preMatch:  m[2]
        match:     m[3]
        postMatch: m[4]
    else
      []
  ).property('resultPage.@each')

App.CwbResultTableController = Em.Controller.extend

  needs: ['resultTable']

  # We get the actual page of results from the resultTableController; this
  # controller is only responsible for doing CWB-specific formatting of
  # those results
  resultPageBinding: 'controllers.resultTable.content'

  arrangedContent: (->
    resultPage = @get('resultPage')

    if resultPage
      resultPage.map (row) ->
        m = row.match(/<s_id (.+)>:\s+(.*)<(.+?)>(.*)/)
        sId:       m[1]
        preMatch:  m[2]
        match:     m[3]
        postMatch: m[4]
    else
      []
  ).property('resultPage.@each')

Allow empty pre- or postmatch

Allow empty pre- or postmatch


#--------------------------------------------------------
# Requirements
#--------------------------------------------------------

gulp      = require 'gulp'
plugins   = require('gulp-load-plugins')()
config    = require "../config.coffee"

#--------------------------------------------------------
# Icon Font
#--------------------------------------------------------

gulp.task "icon-font", ->

  cssTemplateFilename = "icon-font-template.css"
  cssOutputFilename = "_icons.styl"
  fontName = "icons"
  fontPath = "../fonts/"
  className = "icon"

  # Grab SVGs from 'svg' directory
  fileSvgStream = gulp.src "#{config.sourcePath}/#{config.svgDirectory}/*.svg"

  # Generate Font and CSS from all SVGs
  fileSvgStream
    .pipe(plugins.iconfont
      fontName: "icons"
      normalize: true
    ).on("codepoints", (codepoints, options) ->
      gulp.src "#{config.sourcePath}/#{config.svgDirectory}/#{cssTemplateFilename}"
        .pipe(plugins.consolidate "lodash",
          glyphs: codepoints
          fontName: fontName
          fontPath: fontPath
          className: className
        ).pipe plugins.rename cssOutputFilename
        .pipe gulp.dest "#{config.sourcePath}/#{config.cssDirectory}"
    ).pipe gulp.dest "#{config.outputPath}/#{config.fontsDirectory}"

#--------------------------------------------------------
# Requirements
#--------------------------------------------------------

gulp      = require 'gulp'
plugins   = require('gulp-load-plugins')()
config    = require "../config.coffee"

#--------------------------------------------------------
# Icon Font
#--------------------------------------------------------

gulp.task "icon-font", ->

  cssTemplateFilename = "icon-font-template.css"
  cssOutputFilename = "_icons.styl"
  fontName = "icons"
  fontPath = "../fonts/"
  className = "icon"

  # Grab SVGs from 'svg' directory
  fileSvgStream = gulp.src "#{config.sourcePath}/#{config.svgDirectory}/*.svg"

  # Generate Font and CSS from all SVGs
  fileSvgStream
    .pipe(plugins.iconfont
      fontName: fontName
      normalize: true
    ).on("glyphs", (glyphs) ->
      glyphs = glyphs.map((glyph) ->
        name: glyph.name,
        codepoint: glyph.unicode[0].charCodeAt(0)
      )
      gulp.src "#{config.sourcePath}/#{config.svgDirectory}/#{cssTemplateFilename}"
        .pipe(plugins.consolidate "lodash",
          glyphs: glyphs
          fontName: fontName
          fontPath: fontPath
          className: className
        )
        .pipe plugins.rename cssOutputFilename
        .pipe gulp.dest "#{config.sourcePath}/#{config.cssDirectory}"
    ).pipe gulp.dest "#{config.outputPath}/#{config.fontsDirectory}"

Use new API for gulp-iconfont

Use new API for gulp-iconfont


exports.config =
  # Note: unlike the karma config, protractor config files
  # are relative to the config file location.
  #
  # Note: the debug and logLevel options are set in the grunt
  # protractor task based on whether the grunt command is
  # executed with the --debug option.

  # Run under the mocha framework rather than the default jasmine.
  framework: 'mocha'

  # If the selenium address is not specified, then protractor
  # issues the following error message:
  #    Could not find chromedriver
  seleniumAddress: 'http://localhost:4444/wd/hub'

  # The test server is on port 3001, not 3000.
  baseUrl: 'http://localhost:3001/qiprofile/'

  # Run all e2e specs. The command line --specs option overrides
  # this setting.
  specs: ['src/**/*.e2e-spec.*']

  # Flag which directs Protractor to wait for the app to be
  # stable before each action.
  useAllAngular2AppRoots: true

  # Run the qirest seed and link the test fixtures into public,
  # if necessary.
  onPrepare: 'src/testing/seed'

  # Pass options into mocha.
  mochaOpts:
    # Allow five seconds per test rather than the default two seconds.
    timeout: 15000

    # The spec reporter has nice checkmarks.
    reporter: 'spec'

    # Raise the threshold for showing slow tests in red to 1.2 seconds.
    slow: 1200

exports.config =
  # Note: unlike the karma config, protractor config files
  # are relative to the config file location.
  #
  # Note: the debug and logLevel options are set in the grunt
  # protractor task based on whether the grunt command is
  # executed with the --debug option.

  # Run under the mocha framework rather than the default jasmine.
  framework: 'mocha'

  # If the selenium address is not specified, then protractor
  # issues the following error message:
  #    Could not find chromedriver
  seleniumAddress: 'http://localhost:4444/wd/hub'

  # The test server is on port 3001, not 3000.
  baseUrl: 'http://localhost:3001/qiprofile/'

  # Run all e2e specs. The command line --specs option overrides
  # this setting.
  specs: ['src/**/*.e2e-spec.*']

  # Flag which directs Protractor to wait for the app to be
  # stable before each action.
  useAllAngular2AppRoots: true

  # Run the qirest seed and link the test fixtures into public,
  # if necessary.
  onPrepare: 'src/testing/seed'

  # Pass options into mocha.
  mochaOpts:
    # Allow 20 seconds per test rather than the default two seconds.
    timeout: 20000

    # The spec reporter has nice checkmarks.
    reporter: 'spec'

    # Raise the threshold for showing slow tests in red to 1.2 seconds.
    slow: 1200

Bump time-out up to 20 seconds for image load.


tryRequire = (dep) ->
  try
    require dep
  catch
    undefined

@window ||= {}

Q = @window.Q
_ = @window._

Q ||= tryRequire 'q'
_ ||= tryRequire 'lodash/dist/lodash.underscore'
_ ||= tryRequire 'underscore'

utils = require('./yayson/utils')(_, Q)

Adapter = require('./yayson/adapter')
adapters = require('./yayson/adapters')
presenterFactory = require('./yayson/presenter')

lookupAdapter = (nameOrAdapter) ->
  adapters[nameOrAdapter] || Adapter

presenter = (options = {}) ->
  adapter = lookupAdapter options.adapter
  presenterFactory(utils, adapter)

module.exports = ({adapter} = {}) ->
  Store: require('./yayson/store')(utils)
  Presenter: presenter({adapter})
  Adapter: Adapter

@window ||= {}

Q = @window.Q
_ = @window._

Q ||= (try require? 'q')
_ ||= (try require? 'lodash/dist/lodash.underscore')
_ ||= (try require? 'underscore')

utils = require('./yayson/utils')(_, Q)

Adapter = require('./yayson/adapter')
adapters = require('./yayson/adapters')
presenterFactory = require('./yayson/presenter')

lookupAdapter = (nameOrAdapter) ->
  adapters[nameOrAdapter] || Adapter

presenter = (options = {}) ->
  adapter = lookupAdapter options.adapter
  presenterFactory(utils, adapter)

module.exports = ({adapter} = {}) ->
  Store: require('./yayson/store')(utils)
  Presenter: presenter({adapter})
  Adapter: Adapter

Change optional require format to make webpack happier


# jQuery plugin checkbox
$.fn.spinaSwitch = ->
  return this.each ->
    unless $(this).attr('data-plugin-switch')
      input = $(this)
      input.attr('data-plugin-switch', true)
      input.hide()

      # Check if it is checked
      if input.is(':checked')
        klass = "switch active"
      else
        klass = "switch"

      # Insert new HTML into the DOM
      input.after('

                  ')

# Click handlers for checkbox
$(document).on 'click', 'a.switch', (e) ->
  toggleSwitch(e)

$(document).on 'touchend', 'a.switch', (e) ->
  toggleSwitch(e)

toggleSwitch = (e) ->
  checkbox = $(e.currentTarget)
  input = $(checkbox.attr("href"))

  if checkbox.hasClass('activated') || checkbox.hasClass('active')
    checkbox.removeClass('active')
    checkbox.removeClass('activated')
    checkbox.addClass('deactivated')
    input.attr("checked", false)
  else
    checkbox.addClass('activated')
    checkbox.removeClass('deactivated')
    input.attr("checked", true)

  return false

# jQuery plugin checkbox
$.fn.spinaSwitch = ->
  return this.each ->
    unless $(this).attr('data-plugin-switch')
      input = $(this)
      input.attr('data-plugin-switch', true)
      input.hide()

      # Check if it is checked
      if input.is(':checked')
        klass = "switch active"
      else
        klass = "switch"

      # Insert new HTML into the DOM
      input.after('

                  ')

# Click handlers for checkbox
$(document).on 'click', 'a.switch', (e) ->
  toggleSwitch(e)

$(document).on 'touchend', 'a.switch', (e) ->
  toggleSwitch(e)

toggleSwitch = (e) ->
  checkbox = $(e.currentTarget)
  input = $(checkbox.attr("href"))

  if checkbox.hasClass('activated') || checkbox.hasClass('active')
    checkbox.removeClass('active')
    checkbox.removeClass('activated')
    checkbox.addClass('deactivated')
    input.prop("checked", false)
  else
    checkbox.addClass('activated')
    checkbox.removeClass('deactivated')
    input.prop("checked", true)

  return false

Fix setting property checked for spinaSwitch


class IDE.ChatParticipantView extends JView

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

    options.cssClass = 'participant-view'

    super options, data

    participantData = @getData()
    participantName = participantData.nickname

    @avatar    = new AvatarView
      origin   : participantData.nickname
      size     : width: 32, height: 32

    @name = new KDCustomHTMLView
      cssClass : 'name'
      partial  : participantName

    if participantName is KD.nick()
      @watchButton = new KDCustomHTMLView cssClass: 'hidden'
      @settings    = new KDCustomHTMLView cssClass: 'hidden'
      @thisIsYou   = new KDCustomHTMLView
        tagName    : 'span'
        partial    : 'This is you'
        cssClass   : 'you'
    else
      @thisIsYou   = new KDCustomHTMLView cssClass: 'hidden'
      @watchButton = new KDButtonView
        iconOnly : 'yes'
        cssClass : 'watch-button'
        callback : =>
          @watchButton.toggleClass 'watching'
          log 'watch/unwatch user'

      @settings       = new KDSelectBox
        defaultValue  : 'edit'
        selectOptions : [
          { title : 'CAN READ', value : 'read'}
          { title : 'CAN EDIT', value : 'edit'}
        ]

  pistachio: ->
    return """
      {{> @avatar}}
      {{> @name}}
      {{> @thisIsYou}}

        {{> @watchButton}}
        {{> @settings}}

    """

class IDE.ChatParticipantView extends JView

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

    options.cssClass = 'participant-view'

    super options, data

    participantData = @getData()
    participantName = participantData.nickname

    @avatar    = new AvatarView
      origin   : participantData.nickname
      size     : width: 32, height: 32

    @name = new KDCustomHTMLView
      cssClass : 'name'
      partial  : participantName

    @watchButton = new KDButtonView
      iconOnly : 'yes'
      cssClass : 'watch-button'
      callback : =>
        @watchButton.toggleClass 'watching'
        log 'watch/unwatch user'

    @settings       = new KDSelectBox
      defaultValue  : 'edit'
      selectOptions : [
        { title : 'CAN READ', value : 'read'}
        { title : 'CAN EDIT', value : 'edit'}
      ]

  pistachio: ->
    return """
      {{> @avatar}}
      {{> @name}}

        {{> @watchButton}}
        {{> @settings}}

    """

Remove yourself from chat list.

ChatView: Remove yourself from chat list.


Message = require './message'

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

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

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

Message = require './message'

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

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

Remove rendering logic from PanelView

:fire: Remove rendering logic from PanelView


{parseString} = require 'xml2js'

exports.xmlFix = (xml) ->
  if not xml.match /\<root\>/
    xml = "<root>#{xml}</root>"
  if not xml.match /\?xml/
    xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{xml}"
  xml

exports.xmlTransform = (xml, callback) ->
  parseString xml, callback

exports.xmlVal = (elem, attribName, fallback) ->
  return elem[attribName][0] if elem[attribName]
  fallback

{parseString} = require 'xml2js'

exports.xmlFix = (xml) ->
  if not xml.match /\<root\>/
    xml = "<root>#{xml}</root>"
  if not xml.match /\?xml/
    xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{xml}"
  xml

exports.xmlTransform = (xml, callback) ->
  parseString xml, callback

exports.xmlVal = (elem, attribName, fallback) ->
  return fallback unless elem and elem[attribName]
  elem[attribName][0]

Use fallback also when element is undefined.


Meteor.methods
	addUserToRoom: (data) ->
		fromId = Meteor.userId()
		# console.log '[methods] addUserToRoom -> '.green, 'fromId:', fromId, 'data:', data

		room = RocketChat.models.Rooms.findOneById data.rid

		# if room.username isnt Meteor.user().username and room.t is 'c'
		if room.t is 'c' and room.u?.username isnt Meteor.user().username
			throw new Meteor.Error 403, '[methods] addUserToRoom -> Not allowed'

		# verify if user is already in room
		if room.usernames.indexOf(data.username) isnt -1
			return

		newUser = RocketChat.models.Users.findOneByUsername username: data.username

		RocketChat.models.Rooms.addUsernameById data.rid, data.username

		now = new Date()

		RocketChat.models.Subscriptions.createWithRoomAndUser room, newUser,
			ts: now
			open: true
			alert: true
			unread: 1

		RocketChat.models.Messages.createUserAddedWithRoomIdAndUser data.rid, newUser,
			ts: now

		return true

Meteor.methods
	addUserToRoom: (data) ->
		fromId = Meteor.userId()
		console.log '[methods] addUserToRoom -> '.green, 'data:', data

		unless Match.test data?.rid, String
			throw new Meteor.Error 'invalid-rid'

		unless Match.test data?.username, String
			throw new Meteor.Error 'invalid-username'

		room = RocketChat.models.Rooms.findOneById data.rid

		# if room.username isnt Meteor.user().username and room.t is 'c'
		if room.t is 'c' and room.u?.username isnt Meteor.user().username
			throw new Meteor.Error 403, '[methods] addUserToRoom -> Not allowed'

		# verify if user is already in room
		if room.usernames.indexOf(data.username) isnt -1
			return

		newUser = RocketChat.models.Users.findOneByUsername data.username

		RocketChat.models.Rooms.addUsernameById data.rid, data.username

		now = new Date()

		RocketChat.models.Subscriptions.createWithRoomAndUser room, newUser,
			ts: now
			open: true
			alert: true
			unread: 1

		RocketChat.models.Messages.createUserAddedWithRoomIdAndUser data.rid, newUser,
			ts: now

		return true

Fix bug where username incorrectly passed to findOneByUsername. Also adds arugment validation.


siteEngine: "jekyll"
siteLocalDir: "/Users/krainboltgreene/code/organization/blankjekyll.github.io"
siteDraftsDir: "_drafts/"
sitePostsDir: "_posts/"
siteImagesDir: "/"
siteUrl: "http://blankjekyll.github.io"
frontMatter: """
---
layout: <layout>
title: "<title>"
summary: ""
date: <date>
authors:
  - "Kurtis Rainbolt-Greene"
---
"""

siteEngine: "jekyll"
siteLocalDir: "/Users/krainboltgreene/code/krainboltgreene/blankjekyll"
siteDraftsDir: "_drafts/"
sitePostsDir: "_posts/"
siteImagesDir: "/"
siteUrl: "http://blankjekyll.github.io"
frontMatter: """
---
layout: <layout>
title: "<title>"
summary: ""
date: <date>
authors:
  - "Kurtis Rainbolt-Greene"
---
"""

Make it easier to test

Make it easier to test


setEqual = (a, b) ->
  return false unless a.size is b.size
  iterator = a.values()
  until (next = iterator.next()).done
    return false unless b.has(next.value)
  true

subtractSet = (set, valuesToRemove) ->
  valuesToRemove.forEach (value) -> set.delete(value)

addSet = (set, valuesToAdd) ->
  valuesToAdd.forEach (value) -> set.add(value)

intersectSet = (set, other) ->
  set.forEach (value) -> set.delete(value) unless other.has(value)

module.exports = {setEqual, subtractSet, addSet, intersectSet}

setEqual = (a, b) ->
  return false unless a.size is b.size
  iterator = a.values()
  until (next = iterator.next()).done
    return false unless b.has(next.value)
  true

subtractSet = (set, valuesToRemove) ->
  if set.size > valuesToRemove.size
    valuesToRemove.forEach (value) -> set.delete(value)
  else
    set.forEach (value) -> set.delete(value) if valuesToRemove.has(value)

addSet = (set, valuesToAdd) ->
  valuesToAdd.forEach (value) -> set.add(value)

intersectSet = (set, other) ->
  set.forEach (value) -> set.delete(value) unless other.has(value)

module.exports = {setEqual, subtractSet, addSet, intersectSet}

:racehorse: Enumerate the smallest set when subtracting


#= require trix/views/block_view

class Trix.DocumentView
  constructor: (@element, @document) ->

  render: ->
    @element.removeChild(@element.lastChild) while @element.lastChild
    @document.eachBlock (block, index) =>
      textView = new Trix.BlockView block, index
      @element.appendChild(textView.render())

  focus: ->
    @element.focus()

#= require trix/views/block_view

class Trix.DocumentView
  constructor: (@element, @document) ->
    @element.trixPosition = 0
    @element.trixIndex = 0

  render: ->
    @element.removeChild(@element.lastChild) while @element.lastChild
    @document.eachBlock (block, index) =>
      textView = new Trix.BlockView block, index
      @element.appendChild(textView.render())

  focus: ->
    @element.focus()

Set location properties on the document element so the location can be determined when it's empty


goog.provide 'app.react.Header'

class app.react.Header

  ###*
    @param {app.Routes} routes
    @param {app.react.Link} link
    @param {app.users.Store} usersStore
    @constructor
  ###
  constructor: (routes, link, usersStore) ->
    {header,nav} = React.DOM

    @component = React.createFactory React.createClass
      render: ->
        header {},
          nav {},
            link.to
              route: routes.home
              text: Header.MSG_MY_SONGS
              activeFor: [routes.editSong, routes.newSong, routes.trash]
            link.to routes.songs, Header.MSG_SONGS
            link.to routes.about, Header.MSG_ABOUT
            if usersStore.isLogged()
              link.to routes.me, Header.MSG_ME

  @MSG_ABOUT: goog.getMsg 'About'
  @MSG_ME: goog.getMsg 'Me'
  @MSG_MY_SONGS: goog.getMsg 'My Songs'
  @MSG_SONGS: goog.getMsg 'Songs'

goog.provide 'app.react.Header'

class app.react.Header

  ###*
    @param {app.Routes} routes
    @param {app.react.Link} link
    @param {app.users.Store} usersStore
    @constructor
  ###
  constructor: (routes, link, usersStore) ->
    {header,nav} = React.DOM

    @component = React.createFactory React.createClass
      render: ->
        header {},
          nav {},
            link.to
              route: routes.home
              text: Header.MSG_MY_SONGS
              activeFor: [routes.editSong, routes.newSong, routes.trash]
            link.to
              route: routes.songs
              text: Header.MSG_SONGS
              activeFor: [routes.recentlyUpdatedSongs]
            link.to routes.about, Header.MSG_ABOUT
            if usersStore.isLogged()
              link.to routes.me, Header.MSG_ME

  @MSG_ABOUT: goog.getMsg 'About'
  @MSG_ME: goog.getMsg 'Me'
  @MSG_MY_SONGS: goog.getMsg 'My Songs'
  @MSG_SONGS: goog.getMsg 'Songs'

Fix recentlyUpdatedSongs route is not highlighted.


# default - message + brief help

gulp = require "gulp"
runSequence = require "run-sequence"

gulp.task "default", (cb) ->
  console.log('Building BokehJS for developer mode ...')
  runSequence(["scripts", "styles"], "install", "watch", cb)

# default - message + brief help

gulp = require "gulp"
runSequence = require "run-sequence"

gulp.task "default", (cb) ->
  console.log('Building BokehJS for developer mode ...')
  runSequence("build", "install", "watch", cb)

Switch to simply using build

Switch to simply using build


packages: [
  "advanced-open-file"
  "atom-beautify"
  "atom-elixir"
  "atom-elixir-formatter"
  "atom-react-native-autocomplete"
  "auto-indent"
  "auto-update-packages"
  "autocomplete-modules"
  "busy-signal"
  "color-picker"
  "elm-format"
  "elmjutsu"
  "emmet-simplified"
  "expand-region"
  "file-icons"
  "git-diff-popup"
  "git-plus"
  "git-time-machine"
  "hey-pane"
  "hyperclick"
  "incremental-search"
  "indent-guide-improved"
  "intentions"
  "js-hyperclick"
  "language-babel"
  "language-elixir"
  "language-elm"
  "language-javascript-jsx"
  "language-markdown"
  "language-plantuml"
  "linter"
  "linter-elixir-credo"
  "linter-eslint"
  "linter-rubocop"
  "linter-sass-lint"
  "linter-ui-default"
  "list-edit"
  "local-history"
  "markdown-scroll-sync"
  "package-sync"
  "pigments"
  "plantuml-preview"
  "prettier-atom"
  "project-manager"
  "ramda-repl"
  "react-es6-snippets"
  "relative-numbers"
  "ruby-test"
  "smart-tab-name"
  "sort-lines"
  "split-diff"
  "teletype"
  "terminal-tab"
  "toggle-quotes"
  "vim-mode-plus"
]

packages: [
  "advanced-open-file"
  "atom-beautify"
  "atom-elixir"
  "atom-elixir-formatter"
  "atom-react-native-autocomplete"
  "auto-indent"
  "auto-update-packages"
  "autocomplete-modules"
  "busy-signal"
  "color-picker"
  "elm-format"
  "elmjutsu"
  "emmet-simplified"
  "expand-region"
  "file-icons"
  "git-diff-popup"
  "git-plus"
  "git-time-machine"
  "hey-pane"
  "hyperclick"
  "incremental-search"
  "indent-guide-improved"
  "intentions"
  "js-hyperclick"
  "language-babel"
  "language-elixir"
  "language-elm"
  "language-javascript-jsx"
  "language-markdown"
  "language-plantuml"
  "linter"
  "linter-elixir-credo"
  "linter-eslint"
  "linter-rubocop"
  "linter-sass-lint"
  "linter-ui-default"
  "list-edit"
  "local-history"
  "markdown-scroll-sync"
  "package-sync"
  "pigments"
  "plantuml-preview"
  "prettier-atom"
  "project-manager"
  "react-es6-snippets"
  "relative-numbers"
  "ruby-test"
  "smart-tab-name"
  "sort-lines"
  "split-diff"
  "teletype"
  "terminal-tab"
  "toggle-quotes"
  "vim-mode-plus"
]

Remove ramda-repl - no longer used

Remove ramda-repl - no longer used


xdescribe 'CombinedOpenEnded', ->
  beforeEach ->
    spyOn Logger, 'log'
    # load up some fixtures
    loadFixtures 'combined-open-ended.html'
    @element = $('.combined-open-ended')

    describe 'constructor', ->
      beforeEach ->
        @combined = new CombinedOpenEnded @element

      it 'set the element', ->
        except(@combined.element).not.toEqual @element

      #it 'initialize the ajax url, state, and task count', ->

describe 'CombinedOpenEnded', ->
  beforeEach ->
    spyOn Logger, 'log'
    # load up some fixtures
    loadFixtures 'combined-open-ended.html'
    @element = $('.combined-open-ended')

  describe 'constructor', ->
    beforeEach ->
      @combined = new CombinedOpenEnded @element
    it 'set the element', ->
      expect(@combined.element).toEqual @element

Fix up new jasmine tests

Fix up new jasmine tests


$(document).on "turbolinks:load", ->
  $('div.calendar').calendar()
  $('.calendar_range_start').calendar
    endCalendar: $('.calendar_range_end')

  $('.calendar_range_end').calendar
    startCalendar: $('.calendar_range_start')

  $('.ui.accordion').accordion()
  $('.ui.dropdown').dropdown
    action: 'hide'

  $('#calendar').fullCalendar
    firstDay: '1',
    eventSources: [
      '/calendars.json'
    ],
    timeFormat: 'H:mm',
    displayEventEnd: 'true',
    eventRender: (event, element) ->
      if event.description
        element.qtip
          content: {
            text: event.description
          },
          position: {
            my: 'center center',
            at: 'top center'
          },
          style: {
            classes: 'qtip-green qtip-shadow qtip-rounded'
          }

      return

$(document).on "turbolinks:load", ->
  $('div.calendar').calendar()
  $('.calendar_range_start').calendar
    endCalendar: $('.calendar_range_end')

  $('.calendar_range_end').calendar
    startCalendar: $('.calendar_range_start')

  $('.ui.accordion').accordion()
  $('.ui.dropdown').dropdown
    action: 'hide'

  $('#calendar').fullCalendar
    firstDay: '1',
    eventSources: [
      '/calendars.json'
    ],
    timeFormat: 'H:mm',
    displayEventEnd: 'true',
    eventRender: (event, element) ->
      element.qtip
        content: {
          title: { text: event.title },
          text: event.description
        },
        position: {
          my: 'center center',
          at: 'top center'
        },
        style: {
          classes: 'qtip-green qtip-shadow qtip-rounded'
        }

      return

Add tooltip with title and description

Add tooltip with title and description


fs = require 'fs'

editorconfig = require 'editorconfig'
detectIndent = require 'detect-indent'

module.exports = (filePath, callback) ->
	editorconfig.parse(filePath)
	.then (editorconfigProperties) ->
		result = {}

		fs.readFile filePath, 'utf8', (err, fileContents) ->
			return callback err if err

			indentStyle = detectIndent(fileContents).type

			if indentStyle isnt editorconfigProperties.indent_style or !indentStyle
				result.indent_style =
					expected: editorconfigProperties.indent_style
					is: indentStyle

			result = true if Object.keys(result).length is 0

			callback null, result
	.catch (err) ->
		return callback err

fs = require 'fs'

editorconfig = require 'editorconfig'
detectIndent = require 'detect-indent'

module.exports = (filePath, callback) ->
	editorconfig.parse(filePath)
	.then (editorconfigProperties) ->
		result = {}

		fs.readFile filePath, 'utf8', (err, fileContents) ->
			return callback err if err

			indentStyle = detectIndent(fileContents).type

			if indentStyle isnt editorconfigProperties.indent_style and indentStyle
				result.indent_style =
					expected: editorconfigProperties.indent_style
					is: indentStyle

			result = true if Object.keys(result).length is 0

			callback null, result
	.catch (err) ->
		return callback err

Fix incorrect results if detect-indent detects no indentation


program   = require 'commander'
GitHubApi = require 'github'

pkg         = require './../package'
Config      = require './config'
Commands    = require './commands'
Initializer = require './initializer'

exports.run = ->
  program
    .version(pkg.version)
    .option('-c, --config [file]',     'specify .pending-pr file path')
    .option('-g, --global',            'read and write from ~/.pending-pr')
    .option('-a, --all',               'all pull requests')
    .option('-u, --unmergeble',        'include unmergeble pull requests')
    .parse(process.argv)

  args = program.args
  program.help() unless args[0]?

  if args[0] is 'init'
    initializer = new Initializer global: program.global
    initializer.run()
  else
    config = new Config program.config

    client = new GitHubApi version: '3.0.0'
    client.authenticate type: 'oauth', token: config.token

    commands = new Commands
      program: program
      config: config
      client: client

    commands.execute(args...)

program   = require 'commander'
GitHubApi = require 'github'

pkg         = require './../package'
Config      = require './config'
Commands    = require './commands'
Initializer = require './initializer'

exports.run = ->
  program
    .version(pkg.version)
    .option('-c, --config [file]',     'specify .pending-pr file path')
    .option('-g, --global',            'read and write from ~/.pending-pr')
    .option('-a, --all',               'all pull requests')
    .option('-u, --unmergeble',        'include unmergeble pull requests')

  program.on "--help", ->
    console.log "  Commands:"
    console.log ""
    console.log "    init                 Create .pending-pr config file."
    console.log "    list                 Show mergeble pull requests. Short-cut: l"
    console.log "    count                Show mergeble pull requests size. Short-cut: c"
    console.log "    ping                 Ping to pull request to be merged. Short-cut: p"
    console.log "    open                 Browse pull request. Short-cut: o"
    console.log ""

  program.parse process.argv

  args = program.args
  program.help() unless args[0]?

  if args[0] is 'init'
    initializer = new Initializer global: program.global
    initializer.run()
  else
    config = new Config program.config

    client = new GitHubApi version: '3.0.0'
    client.authenticate type: 'oauth', token: config.token

    commands = new Commands
      program: program
      config: config
      client: client

    commands.execute(args...)

Add commands description to help

Add commands description to help


Neighborly.Projects = {} if Neighborly.Projects is undefined
Neighborly.Projects.Backers = {} if Neighborly.Projects.Backers is undefined

Neighborly.Projects.Backers.Edit =
  modules: -> [Neighborly.CustomTooltip]
  init: Backbone.View.extend
    el: '.create-backer-page'

    events:
      'click .faqs a': 'openFaqText'

    initialize: ->
      @payment_view = new this.Payment()

    openFaqText: (e)->
      e.preventDefault()
      $(e.currentTarget).parent().find('p').toggleClass('hide')

    Payment: Backbone.View.extend
      el: '.create-backer-page .payment'

      initialize: ->
        _.bindAll this, 'showContent'
        this.$('.methods input').change this.showContent
        this.$('.methods input:first').click()

      showContent: (e)->
        this.showTotalValue(e)
        this.$('.payment-method').hide()

        $payment = $("##{$(e.currentTarget).val()}-payment.payment-method")
        $payment.fadeIn()

        if $payment.data('path')
          $.get($payment.data('path')).success (data) ->
            $payment.html data

      showTotalValue: (e)->
        $input = $('.create-backer-page header .total-with-fee input')
        $input.val("#{$input.data('total-text')} #{$(e.target).data('value-with-taxes')}")

Neighborly.Projects = {} if Neighborly.Projects is undefined
Neighborly.Projects.Backers = {} if Neighborly.Projects.Backers is undefined

Neighborly.Projects.Backers.Edit =
  modules: -> [Neighborly.CustomTooltip]
  init: Backbone.View.extend
    el: '.create-backer-page'

    events:
      'click .faqs a': 'openFaqText'

    initialize: ->
      @payment_view = new this.Payment()

    openFaqText: (e)->
      e.preventDefault()
      $(e.currentTarget).parent().find('p').toggleClass('hide')

    Payment: Backbone.View.extend
      el: '.create-backer-page .payment'

      initialize: ->
        _.bindAll this, 'showContent'
        this.$('.methods input').change this.showContent
        this.$('.methods input:first').click()

      showContent: (e)->
        this.showTotalValue(e)
        this.$('.payment-method').addClass('loading-section')
        $payment = $("##{$(e.currentTarget).val()}-payment.payment-method")

        if $payment.data('path')
          $.get($payment.data('path')).success (data) =>
            this.$('.payment-method').hide()
            $payment.html data
            $payment.show()
            this.$('.payment-method').removeClass('loading-section')

      showTotalValue: (e)->
        $input = $('.create-backer-page header .total-with-fee input')
        $input.val("#{$input.data('total-text')} #{$(e.target).data('value-with-taxes')}")

Add loading section to payment screen

Add loading section to payment screen


_ = require 'underscore'
Mapping = require '../lib/mapping'
SphereClient = require 'sphere-node-client'
Config = require '../config'
fs = require 'fs'

jasmine.getEnv().defaultTimeoutInterval = 10000

describe 'integration tests', ->
  beforeEach ->
    @sphere = new SphereClient Config
    @mapping = new Mapping Config

  it 'nothing to do', (done) ->
    @mapping.mapOrders([]).then (xmlOrders) ->
      expect(xmlOrders).toEqual []
      done()

  it 'full turn around', (done) ->
    @sphere.orders.perPage(3).fetch().then (result) =>
      @mapping.mapOrders(result.body.results).then (xmlOrders) ->
        expect(_.size xmlOrders).toBe 3
        done()

  describe 'elastic.io', ->
    it 'nothing to do', (done) ->
      @mapping.elasticio {}, Config, (error, message) ->
        expect(error).toBe null
        expect(message).toBe 'No data from elastic.io!'
        done()

    it 'full turn around', (done) ->
      @sphere.orders.perPage(5).fetch().then (result) =>
        msg =
          body: result.body
        @mapping.elasticio msg, Config, (error, message) ->
          console.log error
          expect(error).toBe null
          expect(message.attachments).toBeDefined()
          expect(message.attachments['touch-timestamp.txt']).toBeDefined()
          console.log message.attachments['touch-timestamp.txt'].content
          done()

_ = require 'underscore'
Mapping = require '../lib/mapping'
SphereClient = require 'sphere-node-client'
Config = require '../config'
fs = require 'fs'

jasmine.getEnv().defaultTimeoutInterval = 10000

describe 'integration tests', ->
  beforeEach ->
    @sphere = new SphereClient Config
    @mapping = new Mapping Config

  it 'nothing to do', (done) ->
    @mapping.mapOrders([]).then (xmlOrders) ->
      expect(xmlOrders).toEqual []
      done()

  it 'full turn around', (done) ->
    @sphere.orders.perPage(3).fetch().then (result) =>
      @mapping.mapOrders(result.body.results).then (xmlOrders) ->
        expect(_.size xmlOrders).toBe 3
        done()

  describe 'elastic.io', ->
    it 'nothing to do', (done) ->
      @mapping.elasticio {}, Config, (error, message) ->
        done(JSON.stringify error, null, 4) if error
        expect(message).toBe 'No data from elastic.io!'
        done()

    it 'full turn around', (done) ->
      @sphere.orders.perPage(5).fetch().then (result) =>
        msg =
          body: result.body
        @mapping.elasticio msg, Config, (error, message) ->
          done(JSON.stringify error, null, 4) if error
          expect(message.attachments).toBeDefined()
          expect(message.attachments['touch-timestamp.txt']).toBeDefined()
          done()

Use done(error) instead using expect(..).


Neighborly.Payment = Backbone.View.extend
  el: '.payment'

  initialize: ->
    _.bindAll this, 'showContent'
    this.$('.methods input').click (e) =>
      this.showContent(e)
    this.$('.methods input:first').click()

  showContent: (e)->
    this.$('.payment-method-option').removeClass('selected')
    $(e.currentTarget).parents('.payment-method-option').addClass('selected')
    this.$('.container .loading').addClass('show')
    this.$('.payment-method').addClass('loading-section')
    $payment = $("##{$(e.currentTarget).val()}-payment.payment-method")

    if $payment.data('path')
      $.get($payment.data('path')).success (data) =>
        this.$('.payment-method').html('')
        $payment.html data
        Initjs.initializePartial()
        $payment.show()
        this.$('.payment-method').removeClass('loading-section')
        this.$('.container .loading').removeClass('show')

        $('.order-summary').affix({
          offset: {
            top: '-100px'
            bottom: ->
              return (this.bottom = $('footer').outerHeight(true) + 65)
          }
        })

Add JS affix to order summary

Add JS affix to order summary


{CompositeDisposable} = require 'event-kit'

module.exports =
class MinimapSelectionView
  decorations: []

  constructor: (@minimap) ->
    editor = @minimap.getTextEditor()

    @subscriptions = new CompositeDisposable

    @subscriptions.add editor.onDidAddCursor @handleSelection
    @subscriptions.add editor.onDidChangeCursorPosition @handleSelection
    @subscriptions.add editor.onDidRemoveCursor @handleSelection

    @handleSelection()

  destroy: ->
    @removeDecorations()
    @subscriptions.dispose()
    @minimap = null

  handleSelection: =>
    @removeDecorations()

    for selection in @minimap.getTextEditor().getSelections()
      if not selection.isEmpty()
        decoration = @minimap.decorateMarker(selection.marker, type: 'highlight-under', scope: '.minimap .minimap-selection .region')
        @decorations.push decoration if decoration?
      else if atom.config.get('minimap-selection.highlightCursorsLines')
        decoration = @minimap.decorateMarker(selection.marker, type: 'line', scope: '.minimap .minimap-selection .cursor-line')
        @decorations.push decoration if decoration?

  removeDecorations: ->
    return if @decorations.length is 0
    decoration?.destroy() for decoration in @decorations
    @decorations = []

{CompositeDisposable} = require 'event-kit'

module.exports =
class MinimapSelectionView
  decorations: []

  constructor: (@minimap) ->
    editor = @minimap.getTextEditor()

    @subscriptions = new CompositeDisposable

    @subscriptions.add editor.onDidAddCursor @handleSelection
    @subscriptions.add editor.onDidChangeCursorPosition @handleSelection
    @subscriptions.add editor.onDidRemoveCursor @handleSelection

    @handleSelection()

  destroy: ->
    @removeDecorations()
    @subscriptions.dispose()
    @minimap = null

  handleSelection: =>
    @removeDecorations()

    textEditor = @minimap.getTextEditor()
    return if !textEditor.selections? or textEditor.selections.length is 0

    for selection in textEditor.getSelections()
      if not selection.isEmpty()
        decoration = @minimap.decorateMarker(selection.marker, type: 'highlight-under', scope: '.minimap .minimap-selection .region')
        @decorations.push decoration if decoration?
      else if atom.config.get('minimap-selection.highlightCursorsLines')
        decoration = @minimap.decorateMarker(selection.marker, type: 'line', scope: '.minimap .minimap-selection .cursor-line')
        @decorations.push decoration if decoration?

  removeDecorations: ->
    return if @decorations.length is 0
    decoration?.destroy() for decoration in @decorations
    @decorations = []

:bug: Fix potential infinite loop when an editor has no selection

Fixes #6


module.exports =
  class MaxRetriesProcessor

    constructor: ({ @processor, @maxRetries = 3 }) ->

    process: (notification, context) ->
      @processor notification, context
      .tap (it) => @_onSuccess_ notification, it
      .catch (err) =>
        throw @_sanitizeError_ err if @_shouldRetry_ notification, err
        @_onMaxRetryExceeded_ notification, err

    _shouldRetry_: ({ meta: { dequeueCount } }, err) ->
      dequeueCount < @maxRetries

    _onSuccess_: (notification, result) -> throw new Error "subclass responsability"
    _sanitizeError_: (err) -> throw new Error "subclass responsability"
    _onMaxRetryExceeded_: (notification, err) -> throw new Error "subclass responsability"

module.exports =
  class MaxRetriesProcessor

    constructor: ({ @processor, @maxRetries = 3 }) ->

    process: (notification, context) ->
      @processor notification, context
      .tap (it) => @_onSuccess_ notification, it
      .catch (err) =>
        throw @_sanitizeError_ err if @_shouldRetry_ notification, err
        @_onMaxRetryExceeded_ notification, err

    _shouldRetry_: ({ meta: { dequeueCount = 0 } }, err) ->
      dequeueCount < @maxRetries

    _onSuccess_: (notification, result) -> throw new Error "subclass responsability"
    _sanitizeError_: (err) -> throw new Error "subclass responsability"
    _onMaxRetryExceeded_: (notification, err) -> throw new Error "subclass responsability"

Add default 0 for dequeueCount on _shouldRetry_


angular.module('kassa').controller('ProductDetailCtrl', [
  '$scope'
  'ProductService'
  ($scope, Product)->
    STATE_FAILED = 0
    STATE_SAVED = 1
    STATE_SAVING = 2
    originalProduct = null
    equal = angular.equals

    changed = (product)-> !equal(originalProduct, product)

    cancel = ->
      #copy to prevent any unsaved edits from leaking to server object state
      $scope.product = angular.copy(originalProduct)
      $scope.priceEuro = Math.floor(originalProduct.price)
      $scope.priceCent = (originalProduct.price % 1) * 100

    save = (product)->
      saveSuccess = -> $scope.state = STATE_SAVED
      saveFailure = -> $scope.state = STATE_FAILED
      $scope.state = STATE_SAVING
      Product.update(product).then(setProduct).then saveSuccess, saveFailure

    setProduct = (product)->
      originalProduct = product
      cancel() #sets to default state by copying originalProduct
      product

    updatePrice = (product, euros, cents)->
      console.log 'price before', product.price, typeof product.price
      product.price = euros + cents / 100
      console.log 'price after', product.price, typeof product.price

    Product.currentByRoute().then setProduct
    $scope.changed = changed
    $scope.cancel = cancel
    $scope.save = save
    $scope.updatePrice = updatePrice
    $scope.SAVED = STATE_SAVED
    $scope.FAILED = STATE_FAILED
    $scope.SAVING = STATE_SAVING
])

angular.module('kassa').controller('ProductDetailCtrl', [
  '$scope'
  'ProductService'
  ($scope, Product)->
    STATE_FAILED = 0
    STATE_SAVED = 1
    STATE_SAVING = 2
    originalProduct = null
    equal = angular.equals

    changed = (product)-> !equal(originalProduct, product)

    cancel = ->
      #copy to prevent any unsaved edits from leaking to server object state
      $scope.product = angular.copy(originalProduct)
      $scope.priceEuro = Math.floor(originalProduct.price)
      $scope.priceCent = (originalProduct.price % 1) * 100

    save = (product)->
      saveSuccess = -> $scope.state = STATE_SAVED
      saveFailure = -> $scope.state = STATE_FAILED
      $scope.state = STATE_SAVING
      Product.update(product).then(setProduct).then saveSuccess, saveFailure

    setProduct = (product)->
      originalProduct = product
      cancel() #sets to default state by copying originalProduct
      product

    updatePrice = (product, euros, cents)->
      product.price = euros + cents / 100

    Product.currentByRoute().then setProduct
    $scope.changed = changed
    $scope.cancel = cancel
    $scope.save = save
    $scope.updatePrice = updatePrice
    $scope.SAVED = STATE_SAVED
    $scope.FAILED = STATE_FAILED
    $scope.SAVING = STATE_SAVING
])

Remove console.logs from product detail controller


bcrypt = require 'bcrypt'
crypto = require 'crypto'
mongoose = require 'mongoose'
db = require '../lib/database'

Schema = mongoose.Schema

userSchema = new Schema
  name:
    type: String
    required: true
  email:
    type: String
    required: true
    lowercase: true
    trim: true
    unique: true
  password:
    type: String
    required: true
    set: (password) ->
      return unless password
      salt = bcrypt.genSaltSync()
      bcrypt.hashSync password, salt
  activated:
    type: Boolean
    default: false
  last_active:
    type: Date
    default: Date.now
  date_created:
    type: Date
    default: Date.now

userSchema.methods.authenticate = (password, callback) ->
  bcrypt.compareSync password, @password

userSchema.virtual('email_hash').get ->
  crypto.createHash('md5').update(@email).digest('hex') if @email

userSchema.set 'toJSON', virtuals: true

module.exports = db.model 'User', userSchema

bcrypt = require 'bcrypt'
crypto = require 'crypto'
mongoose = require 'mongoose'
uniqueValidator = require 'mongoose-unique-validator'
db = require '../lib/database'

Schema = mongoose.Schema

userSchema = new Schema
  name:
    type: String
    required: true
  email:
    type: String
    required: true
    lowercase: true
    trim: true
    unique: true
  password:
    type: String
    required: true
    set: (password) ->
      return unless password
      salt = bcrypt.genSaltSync()
      bcrypt.hashSync password, salt
  activated:
    type: Boolean
    default: false
  last_active:
    type: Date
    default: Date.now
  date_created:
    type: Date
    default: Date.now

userSchema.methods.authenticate = (password, callback) ->
  bcrypt.compareSync password, @password

userSchema.virtual('email_hash').get ->
  crypto.createHash('md5').update(@email).digest('hex') if @email

userSchema.path('password').validate (value) ->
  /^(?=[^\d_].*?\d)\w(\w|[!@#$%]){5,20}/.test value
, 'Your password must be between 6–20 characters, start with a letter, and include a number.'

userSchema.plugin uniqueValidator, message: '“{VALUE}” is already a user.'

userSchema.set 'toJSON', virtuals: true

module.exports = db.model 'User', userSchema

Validate email uniqueness and password

Validate email uniqueness and password


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

MESSAGES = {
  student: [

Tutor shows your weakest topics so you can practice to improve.

Try to get all of your topics to green!

  ]
  teacher: [

Tutor shows the weakest topics for each period.

Students may need your help in those areas.

  ]
  teacher_student: [

Tutor shows the weakest topics for the student.

They may need your help in those areas.

  ]
}

module.exports = React.createClass

  displayName: 'LearningGuideInfoLink'
  type: React.PropTypes.string.isRequired

  render: ->
    tooltip =
    <BS.Tooltip className='info-link-tooltip' html='true'>

The performance forecast is an estimate of your understanding of a topic.

It is personalized display based on your answers to reading questions,
      homework problems, and previous practices.

      {MESSAGES[@props.type]}
    </BS.Tooltip>

    <BS.OverlayTrigger placement='right' overlay={tooltip}>

    </BS.OverlayTrigger>

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

MESSAGES = {
  student: [

Tutor shows your weakest topics so you can practice to improve.

Try to get all of your topics to green!

  ]
  teacher: [

Tutor shows the weakest topics for each period.

Students may need your help in those areas.

  ]
  teacher_student: [

Tutor shows the weakest topics for the student.

They may need your help in those areas.

  ]
}

module.exports = React.createClass

  displayName: 'LearningGuideInfoLink'
  type: React.PropTypes.oneOf(['student', 'teacher', 'teacher_student']).isRequired

  render: ->
    tooltip =
    <BS.Tooltip className='info-link-tooltip' html='true'>

The performance forecast is an estimate of your understanding of a topic.

It is personalized display based on your answers to reading questions,
      homework problems, and previous practices.

      {MESSAGES[@props.type]}
    </BS.Tooltip>

    <BS.OverlayTrigger placement='right' overlay={tooltip}>

    </BS.OverlayTrigger>

Validate only known types are set as props


module.exports = """
  query($showHeroUnits: Boolean!) {
    home_page {
      artwork_modules(
        max_rails: 6,
        order: [
          ACTIVE_BIDS,
          RECOMMENDED_WORKS,
          FOLLOWED_ARTISTS,
          RELATED_ARTISTS,
          FOLLOWED_GALLERIES,
          SAVED_WORKS,
          LIVE_AUCTIONS,
          CURRENT_FAIRS,
          FOLLOWED_GENES,
          GENERIC_GENES]) {
        key
        params {
          id
          related_artist_id
          followed_artist_id
        }
      }
      hero_units(platform: DESKTOP) @include(if: $showHeroUnits){
        mode
        heading
        title
        title_image_url
        retina_title_image_url: title_image_url(retina: true)
        subtitle
        link_text
        href
        background_image_url
        credit_line
      }
    }
  }
"""

module.exports = """
  query($showHeroUnits: Boolean!) {
    home_page {
      artwork_modules(
        max_rails: 7,
        order: [
          ACTIVE_BIDS,
          RECENTLY_VIEWED_WORKS
          RECOMMENDED_WORKS,
          FOLLOWED_ARTISTS,
          RELATED_ARTISTS,
          FOLLOWED_GALLERIES,
          SAVED_WORKS,
          LIVE_AUCTIONS,
          CURRENT_FAIRS,
          FOLLOWED_GENES,
          GENERIC_GENES]) {
        key
        params {
          id
          related_artist_id
          followed_artist_id
        }
      }
      hero_units(platform: DESKTOP) @include(if: $showHeroUnits){
        mode
        heading
        title
        title_image_url
        retina_title_image_url: title_image_url(retina: true)
        subtitle
        link_text
        href
        background_image_url
        credit_line
      }
    }
  }
"""

Add support for recently viewed artwork rail on homepage


# The Rivets namespace.
Rivets =
  # Binder definitions, publicly accessible on `module.binders`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  binders: {}

  # Component definitions, publicly accessible on `module.components`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  components: {}

  # Formatter definitions, publicly accessible on `module.formatters`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  formatters: {}

  # Adapter definitions, publicly accessible on `module.adapters`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  adapters: {}

  # The default configuration, publicly accessible on `module.config`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  config:
    prefix: 'rv'
    rootInterface: '.'
    preloadData: true

    handler: (context, ev, binding) ->
      @call context, ev, binding.view.models

# The Rivets namespace.
Rivets =
  # Binder definitions, publicly accessible on `module.binders`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  binders: {}

  # Component definitions, publicly accessible on `module.components`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  components: {}

  # Formatter definitions, publicly accessible on `module.formatters`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  formatters: {}

  # Adapter definitions, publicly accessible on `module.adapters`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  adapters: {}

  # The default configuration, publicly accessible on `module.config`. Can be
  # overridden globally or local to a `Rivets.View` instance.
  config:
    prefix: 'rv'
    templateDelimiters: ['{', '}']
    rootInterface: '.'
    preloadData: true

    handler: (context, ev, binding) ->
      @call context, ev, binding.view.models

Add default { template } delimiters.

Add default { template } delimiters.


class FSFolder extends FSFile

  fetchContents:(callback)->

    # @emit "fs.fetchContents.started"
    # @kiteController.run
    #   toDo      : "ls"
    #   withArgs  :
    #     command : @path
    # , (err, response)=>
    #   if err
    #     warn err
    #     @emit "fs.fetchContents.finished", err
    #   else
    #     files = FSHelper.parseLsOutput [@path], response
    #     @emit "fs.fetchContents.finished", files
    #     callback? files

    @emit "fs.fetchContents.started"
    # a = Date.now()
    @kiteController.run
      withArgs  :
        command : "ls #{FSHelper.escapeFilePath @path} -Llpva --group-directories-first --time-style=full-iso"
    , (err, response)=>
      # log "------------------------------------------------------------------"
      # log "l flag response in: #{Date.now()-a} msec."
      if err
        warn err
        @emit "fs.fetchContents.finished", err
      else
        files = FSHelper.parseLsOutput [@path], response
        @emit "fs.fetchContents.finished", files
        callback? files

  # forkRepoCommandMap = ->

  #   git : "git clone"
  #   svn : "svn checkout"
  #   hg  : "hg clone"

  # cloneRepo:(options, callback)->

  #   @kiteController.run "#{forkRepoCommandMap()[repoType]} #{repo} #{escapeFilePath getAppPath manifest}", (err, response)->

class FSFolder extends FSFile

  fetchContents:(callback)->

    # @emit "fs.fetchContents.started"
    # @kiteController.run
    #   toDo      : "ls"
    #   withArgs  :
    #     command : @path
    # , (err, response)=>
    #   if err
    #     warn err
    #     @emit "fs.fetchContents.finished", err
    #   else
    #     files = FSHelper.parseLsOutput [@path], response
    #     @emit "fs.fetchContents.finished", files
    #     callback? files

    @emit "fs.fetchContents.started"
    # a = Date.now()
    @kiteController.run
      withArgs  :
        command : "ls #{FSHelper.escapeFilePath @path} -LHlpva --group-directories-first --time-style=full-iso"
    , (err, response)=>
      # log "------------------------------------------------------------------"
      # log "l flag response in: #{Date.now()-a} msec."
      if err
        warn err
        @emit "fs.fetchContents.finished", err
      else
        files = FSHelper.parseLsOutput [@path], response
        @emit "fs.fetchContents.finished", files
        callback? files

  # forkRepoCommandMap = ->

  #   git : "git clone"
  #   svn : "svn checkout"
  #   hg  : "hg clone"

  # cloneRepo:(options, callback)->

  #   @kiteController.run "#{forkRepoCommandMap()[repoType]} #{repo} #{escapeFilePath getAppPath manifest}", (err, response)->

Add -H option to fix broken symbolic links error

 From man of ls -H provides: show information for the file the link references rather than for the link itself. So, when some broken links exists it adds the link to the tree with "Link -> Non-Exist" label.


About = require '../lib/about'
{$} = require 'atom-space-pen-views'

describe "About", ->
  workspaceElement = null

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

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

  describe "when the about:about-atom command is triggered", ->
    it "shows the About Atom view", ->
      # Attaching the workspaceElement to the DOM is required to allow the
      # `toBeVisible()` matchers to work. Anything testing visibility or focus
      # requires that the workspaceElement is on the DOM. Tests that attach the
      # workspaceElement to the DOM are generally slower than those off DOM.
      jasmine.attachToDOM(workspaceElement)

      expect(workspaceElement.querySelector('.about')).not.toExist()
      atom.commands.dispatch workspaceElement, 'application:about'

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about')
        expect(aboutElement).toBeVisible()

  describe "when the version number is clicked", ->
    it "copies the version number to the clipboard", ->
      atom.commands.dispatch workspaceElement, 'application:about'

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about')
        versionContainer = aboutElement.querySelector('.about-version-container')
        $(versionContainer).click()
        expect(atom.clipboard.read()).toBe atom.getVersion()

About = require '../lib/about'
{$} = require 'atom-space-pen-views'

describe "About", ->
  workspaceElement = null

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

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

  describe "when the about:about-atom command is triggered", ->
    it "shows the About Atom view", ->
      # Attaching the workspaceElement to the DOM is required to allow the
      # `toBeVisible()` matchers to work. Anything testing visibility or focus
      # requires that the workspaceElement is on the DOM. Tests that attach the
      # workspaceElement to the DOM are generally slower than those off DOM.
      jasmine.attachToDOM(workspaceElement)

      expect(workspaceElement.querySelector('.about')).not.toExist()
      atom.workspace.open('atom://about')

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about')
        expect(aboutElement).toBeVisible()

  describe "when the version number is clicked", ->
    it "copies the version number to the clipboard", ->
      atom.workspace.open('atom://about')

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about')
        versionContainer = aboutElement.querySelector('.about-version-container')
        $(versionContainer).click()
        expect(atom.clipboard.read()).toBe atom.getVersion()

Use atom.workspace.open() for specs to make them less flaky


{CompositeDisposable} = require 'atom'

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

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

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

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

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

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

    @displayedMarkers = markers

  destroy: ->
    @subscriptions.dispose()

{CompositeDisposable} = require 'atom'

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

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

    @subscriptions.add @colorBuffer.onDidUpdateColorMarkers => @updateMarkers()
    @decorations = []

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

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

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

    @displayedMarkers = markers

  destroy: ->
    @subscriptions.dispose()
    decoration.destroy() for decoration in @decorations

:bug: Fix decorations not destroyed on deactivation


# Much of the following is derived from: https://github.com/atom/language-csharp/blob/fba368a8397e298fa78679b4ee0a542b2474c150/scoped-properties/language-csharp.cson
'.source.powershell':
  'editor':
    'commentStart': '# '
    'foldEndPattern': '^\\s*\\}|^\\s*\\]|^\\s*\\)'
    'increaseIndentPattern': '(?x)\n\t\t^ .* \\{ [^}"\']* $\n\t|   ^ \\s* \\{ \\} $\n\t'
    'decreaseIndentPattern': '(?x)\n\t\t^ (.*\\*/)? \\s* \\} ( [^}{"\']* \\{ | \\s* while \\s* \\( .* )? [;\\s]* (//.*|/\\*.*\\*/\\s*)? $\n\t'

# Much of the following is derived from: https://github.com/atom/language-csharp/blob/fba368a8397e298fa78679b4ee0a542b2474c150/scoped-properties/language-csharp.cson
'.source.powershell':
  'editor':
    'commentStart': '# '
    'foldEndPattern': '^\\s*\\}|^\\s*\\]|^\\s*\\)'
    'increaseIndentPattern': '(?x)
      ^ .* \\{ [^}"\']* $
      | ^ \\s* \\{ \\} $
      '
    'decreaseIndentPattern': '(?x)
      ^ (.*\\*/)? \\s* \\} ( [^}{"\']* \\{ | \\s* while \\s* \\( .* )? [;\\s]* (//.*|/\\*.*\\*/\\s*)? $
      '

Reformat the indent pattern regexes

Reformat the indent pattern regexes

"regexes"?


resize = () ->
  screenWidth  = $(window).width() + "px"
  screenHeight = $(window).height() + "px"

  $("#intro, #intro .item, #intro-video, #intro-video .item").css
    width: screenWidth
    height: screenHeight

$ ->
  resize()
  $(window).resize resize

  if document.referrer.match /flatterline/i
    $('#intro .hero h2').before "

Welcome Flatterline visitor!
We recently merged with another awesome development company and became Velocity Labs!
"

resize = () ->
  screenWidth  = $(window).width() + "px"
  screenHeight = $(window).height() + "px"

  $("#intro, #intro .item, #intro-video, #intro-video .item").css
    width: screenWidth
    height: screenHeight

urlParam = (target) ->
  url    = window.location.search.substring(1)
  params = url.split '&'

  for param in params
    values = param.split('=')

    if values[0] == target
      return values[1]

  ''

$ ->
  resize()
  $(window).resize resize

  urlMatch = urlParam('ref')
  if document.referrer.match(/flatterline/i) || urlMatch.match(/flatterline/i)
    $('#intro .hero h2').before "

Welcome Flatterline visitor!
We recently merged with another awesome development company and became Velocity Labs!
"

Use a URL parameter as well as the referrer


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


"*":
  core:
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "atom-light-syntax"
    ]
  editor:
    fontFamily: "Menlo"
    invisibles: {}
    softWrap: true
  "exception-reporting":
    userId: "0f9c4bca-c1e4-c885-1374-c2dd26290ade"
  "spell-check": {}
  tabs:
    addNewTabsAtEnd: true
  welcome:
    showOnStartup: false

"*":
  core:
    telemetryConsent: "limited"
  editor:
    fontFamily: "Menlo"
    invisibles: {}
    softWrap: true
  "exception-reporting":
    userId: "0f9c4bca-c1e4-c885-1374-c2dd26290ade"
  "spell-check": {}
  tabs:
    addNewTabsAtEnd: true
  welcome:
    showOnStartup: false

Switch to Atom default theme

Switch to Atom default theme


# Copyright 2012 The Apache Software Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

define ["core/spi", "core/events"],
  (spi, events) ->
    spi.domReady ->
      spi.body().on events.zone.update, (event) ->
        zone = spi.wrap this

        zone.trigger events.zone.willUpdate

        # TODO: purge existing children?

        zone.update event.memo

        zone.show() unless zone.visible()

        zone.trigger events.zone.didUpdate

    # No meaningful value is returned.
    true

# Copyright 2012 The Apache Software Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# ##core/zone
#
# Provides a default handler for the `events.zone.update` event, attached to the
# document body.
define ["core/spi", "core/events"],
  (spi, events) ->
    spi.domReady ->
      spi.body().on events.zone.update, (event) ->
        zone = spi.wrap this

        zone.trigger events.zone.willUpdate

        # TODO: purge existing children?

        zone.update event.memo

        zone.show() unless zone.visible()

        zone.trigger events.zone.didUpdate

    # No meaningful value is returned.
    true

Add a bit of documentation

Add a bit of documentation


'atom-text-editor:not(.mini)':
  'alt': 'linter:set-bubble-transparent'
  'alt-shift-.': 'linter:next-error'

Use alt-shift-. to jump to next error

:new: Use alt-shift-. to jump to next error


behaviours = {}

share.attach = attach = (behaviour, args...) ->
  check behaviour, Match.OneOf Function, String

  if Match.test behaviour, String
    options = behaviours[behaviour].options
    behaviour = behaviours[behaviour].behaviour

  if Match.test behaviour, Function
    context =
      collection: @
      options: options or {}

    behaviour.apply context, args

  else
    console.warn 'Behaviour not found'

  return

class CollectionBehaviours

  @attach: (collections, args...) ->
    check collections, Match.OneOf Mongo.Collection, [Mongo.Collection]

    if Match.test collections, Mongo.Collection
      collections = [collections]

    attach.apply collection, args for collection in collections

  @configure: (name, options) ->
    check name, String
    check options, Object

    if name of behaviours
      behaviours[name].options = options

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

  @define: (name, behaviour, options) ->
    check name, String
    check behaviour, Function

    optionsPattern = Match.ObjectIncluding
      replace: Boolean

    check options, Match.Optional optionsPattern

    if name of behaviours and not options?.replace
      console.warn 'Behaviour already defined, use {replace: true} to override'

    else
      behaviours[name] =
        behaviour: behaviour

behaviours = {}

share.attach = attach = (behaviour, args...) ->
  check behaviour, Match.OneOf Function, String

  if Match.test behaviour, String
    options = behaviours[behaviour].options
    behaviour = behaviours[behaviour].behaviour

  if Match.test behaviour, Function
    context =
      collection: @
      options: options or {}

    behaviour.apply context, args

  else
    console.warn 'Behaviour not found'

  return

class CollectionBehaviours

  @attach: (collections, args...) ->
    check collections, Match.OneOf Mongo.Collection, [Mongo.Collection]

    if Match.test collections, Mongo.Collection
      collections = [collections]

    attach.apply collection, args for collection in collections

  @config: ->
    @configure.apply @, arguments

  @configure: (name, options) ->
    check name, String
    check options, Object

    if name of behaviours
      behaviours[name].options = options

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

  @define: (name, behaviour, options) ->
    check name, String
    check behaviour, Function

    optionsPattern = Match.ObjectIncluding
      replace: Boolean

    check options, Match.Optional optionsPattern

    if name of behaviours and not options?.replace
      console.warn 'Behaviour already defined, use {replace: true} to override'

    else
      behaviours[name] =
        behaviour: behaviour

Add config as alias for configure

Add config as alias for configure


seconds = 1000

module.exports = EventLoopMonitor =
	monitor: (logger) ->
		interval = setInterval () ->
			EventLoopMonitor.Delay()
		, 1 * seconds
		Metrics = require "./metrics"
		Metrics.registerDestructor () ->
			clearInterval(interval)

	Delay: () ->
		Metrics = require "./metrics"
		t1 = process.hrtime()
		setImmediate () ->
			delta = process.hrtime(t1)
			responseTime = delta[0]*1e6 + delta[1]*1e-3
			Metrics.timing("event-loop-microsec", responseTime)

module.exports = EventLoopMonitor =
	monitor: (logger, interval = 1000, log_threshold = 100) ->
		Metrics = require "./metrics"

		previous = Date.now()
		intervalId = setInterval () ->
			now = Date.now()
			offset = now - previous - interval
			if offset > log_threshold
				logger.warn {offset: offset}, "slow event loop"
			previous = now
			Metrics.timing("event-loop-millsec", offset)
		, interval

		Metrics.registerDestructor () ->
			clearInterval(intervalId)

Monitor event loop by looking for skew

Monitor event loop by looking for skew

If we monitor with setImmediate, we miss big blocking loops. For example,
suppose we have 1000 1ms loops then a single bad 1000ms loop. setImmediate
will only be called at the right time 1/1000 of the time (it has to be the
loop just before the bad one). So this monitoring method gives a good average
if the std dev is low, but doesn't pick up spikes.

Instead, we can monitor the skew from the expected time between setIntervals.
In the case above, with a setInterval for 1000ms, we will pick up a skew
proportional to the amount of time that it overlaps the bad loop. So 50%
change of picking up skew > 500ms, and thus getting a good sense of any
spikes.


#This patches jquery_ujs, such that it grabs csrf token from localStorage
#instead of from the meta tag.

# Make sure that every Ajax request sends the CSRF token
$.rails.CSRFProtection = (xhr) ->
  token = safeLocalStorage.factlink_csrf_token
  if token
    xhr.setRequestHeader('X-CSRF-Token', token)

# making sure that all forms have actual up-to-date token(cached forms contain old one)
$.rails.refreshCSRFTokens = ->
  csrfToken = safeLocalStorage.factlink_csrf_token
  csrfParam = safeLocalStorage.factlink_csrf_param
  $('form input[name="' + csrfParam + '"]').val(csrfToken);

#This patches jquery_ujs, such that it grabs csrf token from localStorage
#instead of from the meta tag.

updateRailsCsrfMetaTags = ->
  if !$('meta[name=csrf-token]').length
    $('<meta name="csrf-token">').appendTo(document.head)
    $('<meta name="csrf-param">').appendTo(document.head)
  $('meta[name=csrf-token]').attr('content', safeLocalStorage.factlink_csrf_token)
  $('meta[name=csrf-param]').attr('content', safeLocalStorage.factlink_csrf_param)

updateCsrfTagsBeforeExecution = (func) -> ->
  updateRailsCsrfMetaTags()
  func.apply(@, arguments)

['refreshCSRFTokens', 'CSRFProtection', 'handleMethod'].forEach (name) ->
  $.rails[name] = updateCsrfTagsBeforeExecution $.rails[name]

Update meta tags instead of reimplementing ujs functionality


utils = require 'core/utils'
RootView = require 'views/core/RootView'

module.exports = class OutcomesReportResultView extends RootView
  id: 'admin-outcomes-report-result-view'
  template: require 'templates/admin/outcome-report-result-view'
  events:
    'click .back-btn': 'onClickBackButton'
    'click .print-btn': 'onClickPrintButton'
  initialize: (@options) ->
    return super() unless me.isAdmin()
    @format = _.identity

    if window?.Intl?.NumberFormat?
      intl = new window.Intl.NumberFormat()
      @format = intl.format.bind(intl)

    @courses = @options.courses.map (course) =>
      _.merge course, {completion: @options.courseCompletion[course._id].completion}
    super()

  onClickBackButton: ->
    console.log("Back View is", @options.backView)
    application.router.openView(@options.backView)

  onClickPrintButton: ->
    window.print()

utils = require 'core/utils'
RootView = require 'views/core/RootView'

module.exports = class OutcomesReportResultView extends RootView
  id: 'admin-outcomes-report-result-view'
  template: require 'templates/admin/outcome-report-result-view'
  events:
    'click .back-btn': 'onClickBackButton'
    'click .print-btn': 'onClickPrintButton'
  initialize: (@options) ->
    return super() unless me.isAdmin()
    @format = _.identity

    if window?.Intl?.NumberFormat?
      intl = new window.Intl.NumberFormat()
      @format = intl.format.bind(intl)

    @courses = @options.courses.map (course) =>
      _.merge course, {completion: @options.courseCompletion[course._id].completion}

    # Reorder CS2 in front of WD1/GD1 if it's more completed, to account for us changing the order around.
    cs1 = _.find @courses, {slug: 'introduction-to-computer-science'}
    cs2 = _.find @courses, {slug: 'computer-science-2'}
    gd1 = _.find @courses, {slug: 'game-development-1'}
    wd1 = _.find @courses, {slug: 'web-development-1'}

    if cs2?.completion > _.max([gd1?.completion, wd1?.completion])
      @courses.splice(@courses.indexOf(cs2), 1)
      @courses.splice(_.max([@courses.indexOf(cs1), 0]) + 1, 0, cs2)
    super()

  onClickBackButton: ->
    console.log("Back View is", @options.backView)
    application.router.openView(@options.backView)

  onClickPrintButton: ->
    window.print()

Rearrange cs2/gd1/wd1 in outcomes report if progress seems out of order


FactlinkJailRoot.createFactFromSelection = (current_user_opinion) ->
  success = ->
    FactlinkJailRoot.createButton.hide()
    FactlinkJailRoot.off 'modalOpened', success

  selInfo =
    text: window.document.getSelection().toString()
    title: window.document.title

  text = selInfo.text
  siteUrl = FactlinkJailRoot.siteUrl()
  siteTitle = selInfo.title

  FactlinkJailRoot.on 'modalOpened', success
  FactlinkJailRoot.factlinkCoreEnvoy 'prepareNewFactlink',
    text, siteUrl, siteTitle, current_user_opinion

FactlinkJailRoot.textSelected = ->
  # At least 4 words of at least 2 characters
  /(\w{2,}[\s-_&\/#%.,;:!()]+){4}/.test window.document.getSelection().toString()

FactlinkJailRoot.createFactFromSelection = (current_user_opinion) ->
  success = ->
    window.document.getSelection().removeAllRanges()
    FactlinkJailRoot.createButton.hide()
    FactlinkJailRoot.off 'modalOpened', success

  selInfo =
    text: window.document.getSelection().toString()
    title: window.document.title

  text = selInfo.text
  siteUrl = FactlinkJailRoot.siteUrl()
  siteTitle = selInfo.title

  FactlinkJailRoot.on 'modalOpened', success
  FactlinkJailRoot.factlinkCoreEnvoy 'prepareNewFactlink',
    text, siteUrl, siteTitle, current_user_opinion

FactlinkJailRoot.textSelected = ->
  # At least 4 words of at least 2 characters
  /(\w{2,}[\s-_&\/#%.,;:!()]+){4}/.test window.document.getSelection().toString()

Remove selection when added a discussion