class Cu.View.ViewTile extends Backbone.View
  className: 'view tile'
  tagName: 'a'
  attributes: ->
    href: "/dataset/#{@model.get('plugsInTo').get('box')}/view/#{@model.get 'box'}"

  events:
    'click .hide': 'hideView'

  initialize: ->
    @model.on 'change', @render, this

  render: ->
    @$el.html JST['view-tile'] @model.toJSON()

    # :TODO: Make this suck less
    if /spreadsheet/i.test @model.get('name')
      @$el.addClass 'spreadsheet'
    if /download/i.test @model.get('name')
      @$el.addClass 'download'
    if /newdataset/i.test @model.get('name')
      @$el.addClass 'source'
    @

  hideView: (e) ->
    e.preventDefault()
    e.stopPropagation()
    @$el.slideUp()
    @model.set 'state', 'deleted'
    @model.get('plugsInTo').save {},
      error: (e) =>
        @$el.slideDown()
        console.warn 'View could not be deleted!'

class Cu.View.ViewTile extends Backbone.View
  className: 'view tile'
  tagName: 'a'
  attributes: ->
    href: "/dataset/#{@model.get('plugsInTo').get('box')}/view/#{@model.get 'box'}"

  events:
    'click .hide-view': 'hideView'
    'click .dropdown-menu a': 'dropdownMenuItemClick'
    'click .rename-view': 'renameViewClick'

  initialize: ->
    @model.on 'change', @render, this

  render: ->
    @$el.html JST['view-tile'] @model.toJSON()

    # :TODO: Make this suck less
    if /spreadsheet/i.test @model.get('name')
      @$el.addClass 'spreadsheet'
    if /download/i.test @model.get('name')
      @$el.addClass 'download'
    if /newdataset/i.test @model.get('name')
      @$el.addClass 'source'
    @

  dropdownMenuItemClick: (e) ->
    e.preventDefault()
    e.stopPropagation()

  hideView: (e) ->
    @$el.slideUp()
    @model.set 'state', 'deleted'
    @model.get('plugsInTo').save {},
      error: (e) =>
        @$el.slideDown()
        console.warn 'View could not be deleted!'

  renameViewClick: ->
    # This is a bit of a hack, to avoid writing yet another rename widget.
    # Hopefully it'll also teach people they can directly edit dataset names.
    window.app.navigate "/dataset/#{@model.get('plugsInTo').get('box')}/view/#{@model.get('box')}", trigger: true
    setTimeout ->
      $('#subnav-path .editable').trigger('click')
    , 300

Rename and hide links working

Rename and hide links working


#!highlights/node_modules/coffee-script/bin/coffee

Highlights = require 'highlights'
fs = require 'fs'
path = require 'path'
highlighter = new Highlights()
highlighter.requireGrammarsSync
  modulePath: require.resolve('./atom-language-perl6/package.json')

stdin = process.openStdin()
stdin.setEncoding 'utf8'

stdin.on 'data', (input) ->
    name = input.trim()
    process.exit() if name == 'exit'
    file_to_hl = path.resolve(name)
    process.stderr.write('HL')
    foo = ->
      fs.readFileSync file_to_hl, 'utf8'

    html = highlighter.highlightSync
      fileContents: foo()
      scopeName: 'source.perl6fe'

    console.log html

#!highlights/node_modules/coffee-script/bin/coffee

Highlights = require 'highlights'
fs = require 'fs'
path = require 'path'
highlighter = new Highlights()
highlighter.requireGrammarsSync
  modulePath: require.resolve('./atom-language-perl6/package.json')

stdin = process.openStdin()
stdin.setEncoding 'utf8'
mystderr = process.stderr
stdin.on 'data', (input) ->
    name = input.trim()
    process.exit() if name == 'exit'
    file_to_hl = path.resolve(name)
    foo = ->
      fs.readFileSync file_to_hl, 'utf8'

    html = highlighter.highlightSync
      fileContents: foo()
      scopeName: 'source.perl6fe'

    console.log html

Make highlights even less talkative. aka not at all


styleguideUri = 'atom://styleguide'

createStyleguideView = (state) ->
  StyleguideView = require './styleguide-view'
  new StyleguideView(state)

atom.deserializers.add
  name: 'StyleguideView'
  deserialize: (state) -> createStyleguideView(state)

module.exports =
  activate: ->
    atom.workspace.addOpener (filePath) ->
      createStyleguideView(uri: styleguideUri) if filePath is styleguideUri

    @disposable = atom.commands.add 'atom-workspace', 'styleguide:show', ->
      atom.workspace.open(styleguideUri)

  deactivate: ->
    @disposable.dispose()

{CompositeDisposable} = require 'atom'
StyleguideUri = 'atom://styleguide'

createStyleguideView = (state) ->
  StyleguideView = require './styleguide-view'
  new StyleguideView(state)

atom.deserializers.add
  name: 'StyleguideView'
  deserialize: (state) -> createStyleguideView(state)

module.exports =
  activate: ->
    @subscriptions = new CompositeDisposable
    @subscriptions.add atom.workspace.addOpener (filePath) ->
      createStyleguideView(uri: StyleguideUri) if filePath is StyleguideUri
    @subscriptions.add atom.commands.add 'atom-workspace', 'styleguide:show', ->
      atom.workspace.open(StyleguideUri)

  deactivate: ->
    @subscriptions.dispose()

Remove workspace opener on deactivate

Remove workspace opener on deactivate


$ ->
  $owl = $(".chapter-projects")

  $owl.owlCarousel
    loop:           false,
    nav:            true,
    margin:         10,
    autoWidth:      true,
    dotsContainer:  ".owl-dots-wrapper #owl-dots",
    responsive:
      0:    {items: 2},
      600:  {items: 2},
      960:  {items: 2},
      1200: {items: 3},

  $(".owl-wrapper .left").click (e)->
    e.preventDefault()
    $owl.trigger("next.owl")

  $(".owl-wrapper .right").click (e)->
    e.preventDefault()
    $owl.trigger("prev.owl")

$ ->
  $owl = $(".chapter-projects")

  $owl.owlCarousel
    loop:           false,
    nav:            true,
    margin:         10,
    autoWidth:      true,
    dotsContainer:  ".owl-dots-wrapper #owl-dots",
    responsive:
      0:    {items: 2},
      600:  {items: 2},
      960:  {items: 2},
      1200: {items: 3},

  $(".owl-wrapper .left").click (e)->
    e.preventDefault()
    $owl.trigger("prev.owl")

  $(".owl-wrapper .right").click (e)->
    e.preventDefault()
    $owl.trigger("next.owl")

Fix chapter page nav direction

Fix chapter page nav direction


App.CurrentPaginatorPageView = Em.TextField.extend
  classNames: 'input-mini'

  # If we bind the value to the currentPageNo of the controller, it will
  # a page change for each key press, causing a page change for each
  # digit in a multi-digit page number. We don't want that, so we rather
  # initialize the value on didInsertElement and use the action event
  # (which is triggered on enter with the page number as argument) to update
  # the page numer in the controller.

  action: 'setCurrentPageNo'

  didInsertElement: ->
    @setValue()
    @$().on 'blur', $.proxy(@setValue, @)

  willDestroyElement: ->
    @$().off 'blur'

  setValue: -> @set('value', @get('controller.currentPageNo'))

App.CurrentPaginatorPageView = Em.TextField.extend
  classNames: 'input-mini'

  # If we use a two-way binding from the value to the currentPageNo of the
  # controller, it will cause a page change for each key press, meaning a page
  # change for each digit in a multi-digit page number. We don't want that, so
  # we rather use a one-way binding and then initialize the value on
  # didInsertElement and use the action event (which is triggered on enter with
  # the page number as argument) to update the page number in the controller.

  valueBinding: Em.Binding.oneWay('controller.currentPageNo')

  action: 'setCurrentPageNo'

  didInsertElement: ->
    @setValue()
    @$().on 'blur', $.proxy(@setValue, @)

  willDestroyElement: ->
    @$().off 'blur'

  setValue: -> @set('value', @get('controller.currentPageNo'))

Set up a one-way binding for the paginator input


Wheelmap = @Wheelmap

Wheelmap.MapController = Ember.ArrayController.extend
  boundsRatioBuffer: 0.3
  previousBounds: null
  center: null
  zoom: null
  startNode: null
  nodes: []
  isLoading: false

  init: ()->
    properties =
      center: new L.LatLng(52.50521, 13.4231)
      zoom: 12

    if $.cookie('last_lat')? and $.cookie('last_lon')?
      properties.center = new L.LatLng($.cookie('last_lat'), $.cookie('last_lng'))

    if $.cookie('last_zoom')?
      properties.zoom = parseInt($.cookie('last_zoom'), 10)

    @setProperties(properties)

  mapChanged: (()->
    center = @get('center')
    zoom = @get('zoom')

    $.cookie('last_lat', center.lat)
    $.cookie('last_lng', center.lng)
    $.cookie('last_zoom', zoom)
  ).observes('center', 'zoom')

  actions:
    zooming: (isZooming, bounds)->
      if isZooming # Only reload when zooming is finished
        return

      @send('permalink')
      @send('updateNodes', bounds)

    moving: (isMoving, bounds)->
      if isMoving # Only reload when moving is finished
        @previousBounds = bounds
        return

      @send('permalink')

      if @previousBounds?
        paddedBounds = @previousBounds.pad(@boundsRatioBuffer)

        if paddedBounds.contains(bounds)
          return

      @send('updateNodes', bounds)

Wheelmap = @Wheelmap

Wheelmap.MapController = Ember.ArrayController.extend
  boundsRatioBuffer: 0.3
  previousBounds: null
  center: null
  zoom: null
  startNode: null
  nodes: []
  isLoading: false

  init: ()->
    properties =
      center: new L.LatLng(52.50521, 13.4231)
      zoom: 12

    if $.cookie('last_lat')? and $.cookie('last_lon')?
      properties.center = new L.LatLng($.cookie('last_lat'), $.cookie('last_lon'))

    if $.cookie('last_zoom')?
      properties.zoom = parseInt($.cookie('last_zoom'), 10)

    @setProperties(properties)

  mapChanged: (()->
    center = @get('center')
    zoom = @get('zoom')

    $.cookie('last_lat', center.lat)
    $.cookie('last_lng', center.lng)
    $.cookie('last_zoom', zoom)
  ).observes('center', 'zoom')

  actions:
    zooming: (isZooming, bounds)->
      if isZooming # Only reload when zooming is finished
        return

      @send('permalink')
      @send('updateNodes', bounds)

    moving: (isMoving, bounds)->
      if isMoving # Only reload when moving is finished
        @previousBounds = bounds
        return

      @send('permalink')

      if @previousBounds?
        paddedBounds = @previousBounds.pad(@boundsRatioBuffer)

        if paddedBounds.contains(bounds)
          return

      @send('updateNodes', bounds)

Fix wrong lon attribute name.

Fix wrong lon attribute name.


VisualizationBase = require './visualization-base.js'

class Visualization extends VisualizationBase

  constructor: (_id) ->
    console.log 'Visualization'
    super _id

  resize: =>
    # setup container height
    h = if $('body').hasClass('fullscreen') then $(window).height() else $(window).height() - 178 # -50-64-64
    console.log 'resize', h
    @visualizationCanvas.$el.height h
    super()

module.exports = Visualization

VisualizationBase = require './visualization-base.js'

class Visualization extends VisualizationBase

  constructor: (_id) ->
    console.log 'Visualization'
    super _id
    # activate table tabs selector
    $('#visualization-table-selector a').click (e) ->
      e.preventDefault()
      $(this).tab 'show'

  resize: =>
    # setup container height
    h = if $('body').hasClass('fullscreen') then $(window).height() else $(window).height() - 178 # -50-64-64
    console.log 'resize', h
    @visualizationCanvas.$el.height h
    super()

module.exports = Visualization

Fix visualization/show tables tab selection


FactlinkJailRoot.showProxyMessage = ->
  content = """

      Factlink demo page

Get the extension to add discussions on every site

Or install Factlink on your own site

  """

  frame = new FactlinkJailRoot.ControlIframe content

  frame.$el.css
    top: '10px'
    left: 'auto'
    right: '10px'
    position: 'fixed'

  frame.fadeIn()

FactlinkJailRoot.showProxyMessage = ->
  content = """

      Factlink demo page

Get the extension to add discussions on every site

Or install Factlink on your own site

  """

  frame = new FactlinkJailRoot.ControlIframe content

  frame.$el.css
    top: '10px'
    left: '10px'
    position: 'fixed'

  frame.fadeIn()

Put notice on the left, on the right didn't *feel* right (pun intended).

If someone who owns a site opens Factlink in the proxy and is all like, what the heck is this doing on my site, they should always see some kind of explanation. Hiding away the explanation under the sidebar seems therefore not a good idea.


$ ->
  enableEditables()

enableEditables = ->
  $('span.editable').editable (value, settings) ->
    $span = $(this)
    data = {_method: 'PUT'}
    data[$span.data('attribute')] = value
    # TODO: We should handle failures and timeouts in POSTing, and make our own "Saving..." indicator.
    $.post($span.data('url'), data)
    value
  , tooltip: 'Click to edit'

  $('table.editable td').editable (value, settings) ->
    $td = $(this)
    $tr = $td.parent('tr')
    data = {_method: 'PUT', index: $td.index(), value: value} # NOTE: Index is 0-based.
    # TODO: We should handle failures and timeouts in POSTing, and make our own "Saving..." indicator.
    $.post($tr.data('url'), data, null, 'json')
    value
  , onblur: 'submit'

window.enableEditables = enableEditables

$ ->
  enableEditables()

enableEditables = ->
  $('span.editable').editable (value, settings) ->
    $span = $(this)
    data = {_method: 'PUT'}
    data[$span.data('attribute')] = value
    # TODO: We should handle failures and timeouts in POSTing, and make our own "Saving..." indicator.
    $.post($span.data('url'), data)
    value
  , tooltip: 'Click to edit', placeholder: ''

  $('table.editable td').editable (value, settings) ->
    $td = $(this)
    $tr = $td.parent('tr')
    data = {_method: 'PUT', index: $td.index(), value: value} # NOTE: Index is 0-based.
    # TODO: We should handle failures and timeouts in POSTing, and make our own "Saving..." indicator.
    $.post($tr.data('url'), data, null, 'json')
    value
  , onblur: 'submit', placeholder: ''

window.enableEditables = enableEditables

Configure jEditable to not add "Click to edit" on blank cells.


logger = require("logger-sharelatex")
NotificationsHandler = require("./NotificationsHandler")

module.exports =

	# Note: notification keys should be url-safe

	groupPlan: (user, licence)->
		key : "join-sub-#{licence.subscription_id}"
		create: (callback = ->)->
			messageOpts =
				groupName: licence.name
				subscription_id: licence.subscription_id
			logger.log user_id:user._id, key:key, "creating notification key for user"
			NotificationsHandler.createNotification user._id, @key, "notification_group_invite", messageOpts, null, callback

		read: (callback = ->)->
			NotificationsHandler.markAsReadWithKey user._id, @key, callback

	projectInvite: (invite, project, sendingUser, user) ->
		key: "project-invite-#{invite._id}"
		create: (callback=()->) ->
			messageOpts =
				userName: sendingUser.first_name
				projectName: project.name
				projectId: project._id.toString()
				token: invite.token
			logger.log {user_id: user._id, project_id: project._id, invite_id: invite._id, key: @key}, "creating project invite notification for user"
			NotificationsHandler.createNotification user._id, @key, "notification_project_invite", messageOpts, invite.expires, callback
		read:  (callback=()->) ->
			NotificationsHandler.markAsReadByKeyOnly @key, callback

logger = require("logger-sharelatex")
NotificationsHandler = require("./NotificationsHandler")

module.exports =

	# Note: notification keys should be url-safe

	groupPlan: (user, licence)->
		key : "join-sub-#{licence.subscription_id}"
		create: (callback = ->)->
			messageOpts =
				groupName: licence.name
				subscription_id: licence.subscription_id
			logger.log user_id:user._id, key: @key, "creating notification key for user"
			NotificationsHandler.createNotification user._id, @key, "notification_group_invite", messageOpts, null, callback

		read: (callback = ->)->
			NotificationsHandler.markAsReadWithKey user._id, @key, callback

	projectInvite: (invite, project, sendingUser, user) ->
		key: "project-invite-#{invite._id}"
		create: (callback=()->) ->
			messageOpts =
				userName: sendingUser.first_name
				projectName: project.name
				projectId: project._id.toString()
				token: invite.token
			logger.log {user_id: user._id, project_id: project._id, invite_id: invite._id, key: @key}, "creating project invite notification for user"
			NotificationsHandler.createNotification user._id, @key, "notification_project_invite", messageOpts, invite.expires, callback
		read:  (callback=()->) ->
			NotificationsHandler.markAsReadByKeyOnly @key, callback

Fix reference to `@key` in log expression.


getTextRange = ->
  doc = window.document
  if doc.getSelection
    doc.getSelection()
  else if doc.selection
    doc.selection.createRange().text
  else
    ''

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

  selInfo = FactlinkJailRoot.getSelectionInfo()

  text = selInfo.text
  siteUrl = FactlinkJailRoot.siteUrl()
  siteTitle = selInfo.title
  guided = !!FactlinkConfig.guided

  FactlinkJailRoot.on 'factlinkAdded', success
  FactlinkJailRoot.factlinkCoreEnvoy 'prepareNewFactlink', text, siteUrl, siteTitle, guided

# We make this a global function so it can be used for direct adding of facts
# (Right click with chrome-extension)
FactlinkJailRoot.getSelectionInfo = ->
  text: getTextRange().toString()
  title: window.document.title

getTextRange = ->
  doc = window.document
  if doc.getSelection
    doc.getSelection()
  else if doc.selection
    doc.selection.createRange().text
  else
    ''

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

  selInfo = FactlinkJailRoot.getSelectionInfo()

  text = selInfo.text
  siteUrl = FactlinkJailRoot.siteUrl()
  siteTitle = selInfo.title
  guided = !!FactlinkConfig.guided

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

# We make this a global function so it can be used for direct adding of facts
# (Right click with chrome-extension)
FactlinkJailRoot.getSelectionInfo = ->
  text: getTextRange().toString()
  title: window.document.title

Revert "Don't depend on modalOpened events but on factlinkAdded events for hiding the loading button"

This reverts commit 5ba5108e925cde7082a0e1e837b2d28ae2388c7f.


#!highlights/node_modules/coffee-script/bin/coffee

Highlights = require 'highlights'
fs = require 'fs'
path = require 'path'
highlighter = new Highlights()
highlighter.requireGrammarsSync
  modulePath: require.resolve('./atom-language-perl6/package.json')

stdin = process.openStdin()
stdin.setEncoding 'utf8'
mystderr = process.stderr
mystdout = process.stdout
process_file = (given_path) ->
  full_path = path.resolve given_path
  fs.readFile full_path, 'utf8', (read_err, file_str) ->
    if read_err
      console.error read_err
    else
      highlighter.highlight (fileContents: file_str, scopeName: 'source.perl6fe'), (hl_err, html) ->
        if hl_err
          console.error hl_err
        else
          obj = {}
          obj.file = full_path
          obj.html = html
          mystdout.write(JSON.stringify(obj) + '\n' )

stdin.on 'data', (input) ->
    process_file input.trim()

#!highlights/node_modules/coffee-script/bin/coffee

Highlights = require 'highlights'
fs = require 'fs'
path = require 'path'
highlighter = new Highlights()
highlighter.requireGrammarsSync
  modulePath: require.resolve('./atom-language-perl6/package.json')

stdin = process.openStdin()
stdin.setEncoding 'utf8'
mystderr = process.stderr
mystdout = process.stdout
process_file = (given_path) ->
  full_path = path.resolve given_path
  i = 0
  e = true
  while e && !fs.existsSync(given_path)
      i++
      if i > 100000
          console.error "Highlights runner: ERROR Giving up looking for the file. Cannot read file #{full_path}"
          e = false
  if i > 0
    console.error "Highlights runner: file #{full_path} does not exist. tried #{i} times."

  fs.readFile full_path, 'utf8', (read_err, file_str) ->
    if read_err
      console.error read_err
    else
      highlighter.highlight (fileContents: file_str, scopeName: 'source.perl6fe'), (hl_err, html) ->
        if hl_err
          console.error hl_err
        else
          obj = {}
          obj.file = full_path
          obj.html = html
          mystdout.write(JSON.stringify(obj) + '\n' )

stdin.on 'data', (input) ->
    process_file input.trim()

Add some more fallback for the Highlights runner

If the file we are passed doesn't exist, we check back to see if it is then created.
We give an error regardless if we don't find the file the first time, but this makes it
a little more fault tolerant.


#= require ./add_channel_to_channels_modal_view

class window.AddChannelToChannelsButtonView extends Backbone.Marionette.Layout
  template: 'channels/add_channel_to_channels_button'

  events:
    "click .js-add-to-channel-button": "openAddToChannelModal"

  initialize: ->
    @collection = @model.getOwnContainingChannels(this)
    @bindTo @collection, "add remove reset", (channel) => @updateButton()

  onRender: ->
    @updateButton()

  updateButton: =>
    added = @collection.length > 0

    @$('.added-to-channel-button-label').toggle added
    @$('.add-to-channel-button-label').toggle not added

  openAddToChannelModal: (e) ->
    e.stopImmediatePropagation()
    e.preventDefault()

    FactlinkApp.Modal.show 'Add to Channels',
      new AddChannelToChannelsModalView
            model: @model,
            collection: @collection
            suggestions: @options.suggested_topics

#= require ./add_channel_to_channels_modal_view

class window.AddChannelToChannelsButtonView extends Backbone.Marionette.Layout
  template: 'channels/add_channel_to_channels_button'

  events:
    "click .js-add-to-channel-button": "openAddToChannelModal"

  initialize: ->
    @collection = @model.getOwnContainingChannels(this)
    @bindTo @collection, "add remove reset", (channel) => @updateButton()

  onRender: ->
    @updateButton()

  updateButton: =>
    added = @collection.length > 0

    @$('.added-to-channel-button-label').toggle added
    @$('.add-to-channel-button-label').toggle not added

  openAddToChannelModal: (e) ->
    e.stopImmediatePropagation()
    e.preventDefault()

    suggestion_collection = @options.suggested_topics || new SuggestedTopics([@model.topic()])

    FactlinkApp.Modal.show 'Add to Channels',
      new AddChannelToChannelsModalView
            model: @model,
            collection: @collection
            suggestions: suggestion_collection

Fix channel suggestion in add-to-channel on channel page


{Testing, expect, sinon, _} = require 'openstax-react-components/test/helpers'

$ = require 'jquery'

describe 'API loader', ->
  beforeEach ->
    @jquery =
      ajax: sinon.spy ->
        d = $.Deferred()
        _.defer -> d.resolve({})
        d.promise()
    delete require.cache[require.resolve('api')]
    delete require.cache[require.resolve('api/loader')]
    require.cache[require.resolve('jquery')].exports = @jquery
    @api = require 'api'
    @api.initialize('test/url')

  afterEach ->
    require.cache[require.resolve('jquery')].exports = $
    # force require to re-parse the api file now that the stub's removed
    delete require.cache[require.resolve('api/loader')]

  it 'sets isPending', (done) ->
    expect(@api.isPending()).to.be.false
    @api.channel.emit('user.status.send.fetch', data: {})
    _.delay( =>
      expect(@jquery.ajax.callCount).equal(1)
      expect(@api.isPending()).to.be.true
    , 1)
    _.delay( =>
      expect(@api.isPending()).to.be.false
      done()
    , 210) # longer than loader's isLocal delay

  it 'debounces calls to the same URL', ->
    for i in [1..10]
      @api.channel.emit('user.status.send.fetch', data: {})
    _.delay =>
      expect(@jquery.ajax.callCount).equal(1)

{Testing, expect, sinon, _} = require 'openstax-react-components/test/helpers'

$ = require 'jquery'

describe 'API loader', ->
  beforeEach ->
    @jquery =
      ajax: sinon.spy ->
        d = $.Deferred()
        _.defer -> d.resolve({})
        d.promise()
    delete require.cache[require.resolve('api')]
    delete require.cache[require.resolve('api/loader')]
    require.cache[require.resolve('jquery')].exports = @jquery
    @api = require 'api'
    @api.initialize('test/url')

  afterEach ->
    require.cache[require.resolve('jquery')].exports = $
    # force require to re-parse the api file now that the stub's removed
    delete require.cache[require.resolve('api/loader')]

  it 'sets isPending', (done) ->
    expect(@api.isPending()).to.be.false
    @api.channel.emit('user.status.send.fetch', data: {})
    _.delay( =>
      expect(@api.isPending()).to.be.true
    , 1)
    _.delay =>
      expect(@api.isPending()).to.be.false
      done()
    , 21 # longer than loader's isLocal delay

  it 'debounces calls to the same URL', (done) ->
    for i in [1..10]
      @api.channel.emit('user.status.send.fetch', data: {})
    _.delay =>
      expect(@jquery.ajax.callCount).equal(1)
      done()
    , 21

Add done() and set delay

Add done() and set delay


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

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

    show: (view) ->
      @close()
      @views = null
      @append(view)

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

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

      @$el?.empty()

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

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

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

      return @

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

      @detachPopovers()
      @stopListening()
      @remove()
      @unbind()
      delete @[key] for key of @
      return @

    detachPopovers: () ->
      @_popovers.pop().close() while @_popovers?.length

    attachPopover: (popover) ->
      @_popovers.push popover

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

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

    show: (view) ->
      @close()
      @views = null
      @append(view)

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

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

      @$el?.empty()

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

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

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

      return @

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

      @detachPopovers()
      @stopListening()
      @remove()
      @unbind()
      delete @[key] for key of @
      return @

    detachPopovers: () ->
      @_popovers.pop().close() while @_popovers?.length

    attachPopover: (popover) ->
      @_popovers.push popover

Allow template to be a function

Allow template to be a function


marked = require 'marked'
highlight = require '../highlight'

# Initialize markdown
marked.setOptions
  gfm: true

module.exports =
  converters:
    markdown:
      priority: 2
      matches: (ext) ->
        ext is '.md' or ext is '.markdown'
      outputExtension: (ext) ->
        '.html'
      convert: (content, callback) ->
        converted = marked content
        highlight.highlightCodeBlocks converted, callback

marked = require 'marked'
highlight = require('pygments').colorize

# Initialize markdown
marked.setOptions
  gfm: true
  smartypants: true
  highlight: (code, lang, callback) ->
    highlight code, lang, 'html', (data) ->
      # Strip out the HTML wrapper added around the code
      data = data.replace(
        # Final \s is for newline
        /^<div class="highlight"><pre>([\s\S]+)\s<\/pre><\/div>$/img,
        (match, p1, offset, str) -> p1
      )
      callback null, data

module.exports =
  converters:
    markdown:
      priority: 2
      matches: (ext) ->
        ext is '.md' or ext is '.markdown'
      outputExtension: (ext) ->
        '.html'
      convert: (content, callback) ->
        converted = marked content, {}, callback

Use async API from latest version of marked


#= jquery/jquery

app = angular.module 'barebones', []

app.controller 'mainController', ($scope) ->
    $scope.appName = 'BareBones'

Add angular declaration so it works

Add angular declaration so it works


$(document).on 'ready page:change', ->
  $('input[type=datetime]').prop('type','text').datetimepicker
    useSeconds: false
    sideBySide: false

  $('input[type=date]').prop('type','text').datetimepicker
    pickTime: false

  $('input.datetime').on 'dp.change', (event)->
    hidden = $(event.target).next 'input[type=hidden]'
    hidden.val event.date.format 'YYYY-MM-DDTHH:mm'

  $('input.date').on 'dp.change', (event)->
    hidden = $(event.target).next 'input[type=hidden]'
    hidden.val event.date.format 'YYYY-MM-DD'

$(document).on 'ready page:change', ->
  $('input[type=datetime]').prop('type','text').datetimepicker
    useSeconds: false
    sideBySide: false

  $('input[type=date]').prop('type','text').datetimepicker
    pickTime: false

  handleChange = (format)->
    (event)->
      hidden = $(event.target).next 'input[type=hidden]'
      hidden.val event.date.format format

  handleEmpty = (event)->
    hidden = $(event.target).next 'input[type=hidden]'
    hidden.val '' if event.target.value == ''

  $('input.datetime')
    .on 'dp.change', handleChange('YYYY-MM-DDTHH:mm')
    .on 'change', handleEmpty

  $('input.date')
    .on 'dp.change', handleChange('YYYY-MM-DD')
    .on 'change', handleEmpty

Allow to clear datetime inputs

Allow to clear datetime inputs


MinimapSelectionView = require './minimap-selection-view'

module.exports =

  active: false
  views: {}

  activate: (state) ->
    minimapPackage = atom.packages.getLoadedPackage('minimap')
    return @deactivate() unless minimapPackage?

    @minimap = require minimapPackage.path
    @minimap.registerPlugin 'selection', this

  deactivate: ->
    @minimap.unregisterPlugin 'selection'
    @minimap = null

  isActive: -> @active
  activatePlugin: ->
    return if @active
    @active = true

    @subscription = @minimap.eachMinimapView ({view}) =>
      selectionView = new MinimapSelectionView(view)
      selectionView.attach()

      @views[view.editor.id] = selectionView

  deactivatePlugin: ->
    view.destroy() for id,view of @views
    return unless @active
    @active = false
    @views = {}

    @subscription.off()

MinimapSelectionView = require './minimap-selection-view'

module.exports =

  active: false
  views: {}

  activate: (state) ->
    minimapPackage = atom.packages.getLoadedPackage('minimap')
    return @deactivate() unless minimapPackage?

    @minimap = require minimapPackage.path
    return @deactivate() unless @minimap.versionMatch('3.x')

    @minimap.registerPlugin 'selection', this

  deactivate: ->
    @minimap.unregisterPlugin 'selection'
    @minimap = null

  isActive: -> @active
  activatePlugin: ->
    return if @active
    @active = true

    @subscription = @minimap.eachMinimapView ({view}) =>
      selectionView = new MinimapSelectionView(view)
      selectionView.attach()

      @views[view.editor.id] = selectionView

  deactivatePlugin: ->
    view.destroy() for id,view of @views
    return unless @active
    @active = false
    @views = {}

    @subscription.off()

Add version test for minimap v3

Add version test for minimap v3


# Load all required libraries.
gulp = require 'gulp'
gutil = require 'gulp-util'

clean = require('gulp-clean')
coffee = require 'gulp-coffee'
coffeelint = require('gulp-coffeelint')
istanbul = require 'gulp-istanbul'
mocha = require 'gulp-mocha'

gulp.task 'clean', ->
  gulp.src(['lib/**/*.js'], read: false).pipe(clean())

gulp.task 'coffee', ->
  gulp.src ['src/**/*.coffee']
    .pipe coffee({bare: true}).on('error', gutil.log)
    .pipe gulp.dest 'lib'

gulp.task 'lint', ->
  gulp.src 'src/**/*.coffee'
    .pipe coffeelint()
    .pipe coffeelint.reporter()

gulp.task 'test', ['coffee', 'lint'], ->
  gulp.src ['lib/**/*.js']
    .pipe istanbul() # Covering files
    .on 'finish', ->
      gulp.src ['spec/**/*.spec.coffee']
        .pipe mocha reporter: 'spec', compilers: 'coffee:coffee-script'
        .pipe istanbul.writeReports() # Creating the reports after tests run

gulp.task 'default', ['clean', 'coffee']
gulp.task 'build', ['lint', 'clean', 'coffee', 'test']

# Load all required libraries.
gulp = require 'gulp'
gutil = require 'gulp-util'

clean = require('gulp-clean')
coffee = require 'gulp-coffee'
coffeelint = require('gulp-coffeelint')
istanbul = require 'gulp-istanbul'
mocha = require 'gulp-mocha'

gulp.task 'clean', ->
  gulp.src(['lib/**/*.js'], read: false).pipe(clean())

gulp.task 'coffee', ->
  gulp.src ['src/**/*.coffee']
    .pipe coffee({bare: true}).on('error', gutil.log)
    .pipe gulp.dest 'lib'

gulp.task 'lint', ->
  gulp.src 'src/**/*.coffee'
    .pipe coffeelint()
    .pipe coffeelint.reporter()

istanbul = require 'gulp-istanbul'
mocha = require 'gulp-mocha'

gulp.task 'test', ['coffee', 'lint'], ->
  gulp.src ['lib/**/*.js']
    .pipe istanbul({includeUntested: true}) # Covering files
    .on 'finish', ->
      gulp.src ['spec/**/*.spec.coffee']
        .pipe mocha reporter: 'spec', compilers: 'coffee:coffee-script'
        .pipe istanbul.writeReports() # Creating the reports after tests run

gulp.task 'default', ['clean', 'coffee']
gulp.task 'build', ['lint', 'clean', 'coffee', 'test']

Add include untested option to istanbul

Add include untested option to istanbul


$document = $(document)

$document.on 'click.dropdown', '[data-toggle="collapse"]', (e)->
  # Don't close dropdown when accordion togglers are clicked
  e.stopPropagation()

$document.on 'click.dropdown', '[data-toggle="dropdown"]', (e)->
  # Close all collapse elements, if dropdown was closed
  $parent = $(this).parent('.dropdown')

  unless $parent.hasClass('open')
    $parent.find('.collapse.in').collapse('hide').each ->
      # Must set this flag to false to don't stop collapse trigger next time
      $(this).data('collapse').transitioning = false

$document.on 'show hide', '.collapse', (e)->
  # Add collapsed class to toggler (wheelmap custom hack)
  # Need the toggler to be next to the collapsable element (e.g. via accoridon-group)
  # @TODO Find a way to be more flexible here!
  $trigger = $(@).prev('[data-toggle="collapse"]')
  $trigger[(if e.type is 'show' then 'removeClass' else 'addClass')]('collapsed')

Add fix for arrows in collapsables showing the wrong collapse status.


Game = @Game

class Game.LocationGrid
  constructor: (settings) ->
    xs = @_coordList(settings.x)
    ys = @_coordList(settings.y)
    coords = []
    for y in ys
      for x in xs
        coords.push { x, y }

    @freeCoords = _.shuffle coords

  _coordList: (listSettings) ->
    for i in [0...listSettings.steps]
      listSettings.start + (i * listSettings.stepSize)

  getLocation: ->
    @freeCoords.pop()

Game = @Game

class Game.LocationGrid
  constructor: (settings) ->
    settings = _.defaults(settings,
      x:
        start: 0
        steps: 1
        stepSize: 1
      y:
        start: 0
        steps: 1
        stepSize: 1
    )

    xs = @_coordList(settings.x)
    ys = @_coordList(settings.y)
    coords = []
    for y in ys
      for x in xs
        coords.push { x, y }

    @freeCoords = _.shuffle coords

  _coordList: (listSettings) ->
    for i in [0...listSettings.steps]
      listSettings.start + (i * listSettings.stepSize)

  getLocation: ->
    @freeCoords.pop()

Add defaults for single axis grids

Add defaults for single axis grids


# Description
#   A hubot script to query Board Game Geek
#
# Configuration:
#   LIST_OF_ENV_VARS_TO_SET
#
# Commands:
#   hubot hello - <what the respond trigger does>
#   orly - <what the hear trigger does>
#
# Notes:
#   <optional notes required for the script>
#
# Author:
#   Bart Dorsey[@<org>]

options =
  timeout: 10000

  retry:
    initial: 100
    multiplier: 2
    max: 15e3

bgg = require('bgg')(options)

module.exports = (robot) ->
  robot.respond /bgg search (.*)$/, (msg) ->
    params =
      query: msg.match[1].replace(' ','+')
      type: 'boardgame'
    console.log params
    bgg('search', params)
      .then (results) ->
        if results.items.total > 0
          items = results.items.item
          for item in items
            do (item) ->
              msg.send "<http://www.boardgamegeek.com/#{item.type}/#{item.id}|#{item.name.value>"
        else
          msg.send "We couldn't find any boardgames matching \"#{msg.match[1]}\""

# Description
#   A hubot script to query Board Game Geek
#
# Configuration:
#   LIST_OF_ENV_VARS_TO_SET
#
# Commands:
#   hubot hello - <what the respond trigger does>
#   orly - <what the hear trigger does>
#
# Notes:
#   <optional notes required for the script>
#
# Author:
#   Bart Dorsey[@<org>]

options =
  timeout: 10000

  retry:
    initial: 100
    multiplier: 2
    max: 15e3

bgg = require('bgg')(options)

module.exports = (robot) ->
  robot.respond /bgg search (.*)$/, (msg) ->
    params =
      query: msg.match[1].replace(/\s+/g,'+')
      type: 'boardgame'
    console.log params
    bgg('search', params)
      .then (results) ->
        if results.items.total > 0
          items = results.items.item
          for item in items
            do (item) ->
              msg.send "<http://www.boardgamegeek.com/#{item.type}/#{item.id}|#{item.name.value>"
        else
          msg.send "We couldn't find any boardgames matching \"#{msg.match[1]}\""

Replace all spaces with +

Replace all spaces with +


define (require) ->
  $ = require('jquery')
  router = require('cs!router')
  analytics = require('cs!helpers/handlers/analytics')
  BaseView = require('cs!helpers/backbone/views/base')
  template = require('hbs!./node-template')
  require('less!./node')

  return class TocNodeView extends BaseView
    template: template
    tagName: 'li'
    itemViewContainer: '.subcollection'

    events:
      'click a': 'loadPage'

    initialize: () ->
      super()
      @listenTo(@model, 'change', @render) if @model

    loadPage: (e) ->
      e.preventDefault()
      $a = $(e.currentTarget)

      @model.setPage($a.data('page'))
      route = $a.attr('href')
      router.navigate(route) # Update browser URL to reflect the new route
      analytics.send() # Send the analytics information for the new route

define (require) ->
  $ = require('jquery')
  router = require('cs!router')
  analytics = require('cs!helpers/handlers/analytics')
  BaseView = require('cs!helpers/backbone/views/base')
  template = require('hbs!./node-template')
  require('less!./node')

  return class TocNodeView extends BaseView
    template: template
    tagName: 'li'
    itemViewContainer: '.subcollection'

    events:
      'click a': 'loadPage'

    initialize: () ->
      super()
      @listenTo(@model, 'change', @render) if @model

    loadPage: (e) ->
      e.preventDefault()
      $a = $(e.currentTarget)

      @model.get('book').setPage($a.data('page'))
      route = $a.attr('href')
      router.navigate(route) # Update browser URL to reflect the new route
      analytics.send() # Send the analytics information for the new route

Fix navigating to pages from the ToC

Fix navigating to pages from the ToC


Backbone.Factlink ||= {}

class Backbone.Factlink.CrossFadeRegion extends Backbone.Marionette.Region

  fadeIn: -> @$el?.stop().hide().fadeIn(@_fadeTime())

  fadeOut: (callback=->) -> @$el?.stop().fadeOut(@_fadeTime(), callback)

  crossFade: (newView) ->
    if @currentView
      @fadeOut => @show newView
    else
      @show(newView)

  open: (view) ->
    super(view)
    @fadeIn()

  resetFade: -> @fadeOut => @reset()

  _fadeTime: ->
    if @options?.fadeTime?
      @options.fadeTime
    else
      560

  onClose: -> @$el?.stop()

Backbone.Factlink ||= {}

class Backbone.Factlink.CrossFadeRegion extends Backbone.Marionette.Region

  fadeIn: ->
    @ensureEl()
    @$el.stop().hide().fadeIn(@_fadeTime())

  fadeOut: (callback=->) ->
    @ensureEl()
    @$el.stop().fadeOut(@_fadeTime(), callback)

  crossFade: (newView) ->
    if @currentView
      @fadeOut => @show newView
    else
      @show(newView)

  open: (view) ->
    super(view)
    @fadeIn()

  resetFade: -> @fadeOut => @reset()

  _fadeTime: ->
    if @options?.fadeTime?
      @options.fadeTime
    else
      560

  onClose: -> @$el?.stop()

Call ensureEl instead of checking if @$el exists, otherwise behaviour is unexpected, e.g. callback may not be called


Fix URL redirect when in sub-directory

Fix URL redirect when in sub-directory


# Created by AshGillman, 05/08/12
__           = require '../helpers'
ScatterChart = require('../NvWrapper').ScatterChart
DrawerBase   = require '../DrawerBase'

ScatterDrawer = class ScatterDrawer extends DrawerBase
  constructor: (@parent, sensor_metadata, @ts_params) ->
    super(@parent, sensor_metadata, @ts_params)
    do do @redraw

  # NOTE: returns a closure
  redraw: ->
    context = @
    return ->
      for sensor in context.sensors
        context._bind_dataMgr_to_sensor sensor

  _draw_node_status: ->
    @chart ?= new ScatterChart @parent
    @plot_data ?=
      [{
        key: 'Uncomfortable'
        values: []
      },
      {
        key: 'Indecisive'
        values: []
      },
      {
        key: 'Comfortable'
        values: []
      }]

    console.log @sensors[2].properties.th_times.length, @sensors[2].properties.th_times
    for {properties} in @sensors
      if properties.comf_times.length > 0 and properties.th_times.length > 0
        for comf, i in properties.comfortabilities
          time = properties.comf_times[i]
          th_index = properties.th_times.findIndex (d) -> d > time
          #console.log th_index
          if th_index >= 0
            @plot_data[comf-1].values.push {
              x: properties.temperatures[th_index]
              y: properties.humidities[th_index]
              }
    @chart.updateChart @plot_data

module.exports = ScatterDrawer

# Created by AshGillman, 05/08/12
__           = require '../helpers'
ScatterChart = require('../NvWrapper').ScatterChart
DrawerBase   = require '../DrawerBase'

ScatterDrawer = class ScatterDrawer extends DrawerBase
  constructor: (@parent, sensor_metadata, @ts_params) ->
    super(@parent, sensor_metadata, @ts_params)
    do do @redraw

  # NOTE: returns a closure
  redraw: ->
    context = @
    return ->
      for sensor in context.sensors
        context._bind_dataMgr_to_sensor sensor

  _draw_node_status: ->
    @chart ?= new ScatterChart @parent
    @plot_data =
      [{
        key: 'Uncomfortable'
        values: []
      },
      {
        key: 'Indecisive'
        values: []
      },
      {
        key: 'Comfortable'
        values: []
      }]

    for {properties} in @sensors
      if properties.comf_times.length > 0 and properties.th_times.length > 0
        for comf, i in properties.comfortabilities
          time = properties.comf_times[i]
          th_index = properties.th_times.findIndex (d) -> d > time
          #console.log th_index
          if th_index >= 0
            @plot_data[comf-1].values.push {
              x: properties.temperatures[th_index]
              y: properties.humidities[th_index]
              }
    @chart.updateChart @plot_data

module.exports = ScatterDrawer

Fix DOM leak in scatterplot

Fix DOM leak in scatterplot


React = require 'react'

NoExercises = React.createClass
  displayName: 'NoExercises'
  render: ->

      Sorry, there are no exercises for this module.

CourseEnded = React.createClass
  displayName: 'NoExercises'
  render: ->

      Sorry, there are no exercises for this module.

module.exports =
  no_exercises: NoExercises
  course_ended: CourseEnded

React = require 'react'

NoExercises = React.createClass
  displayName: 'NoExercises'
  render: ->

      Sorry, there are no exercises for this module.

CourseEnded = React.createClass
  displayName: 'CourseEnded'
  render: ->

      This Concept Coach course has ended. Click My Progress
      at the top of the screen to access all previous work for this course.

module.exports =
  no_exercises: NoExercises
  course_ended: CourseEnded

Use correct error message for course ended


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

Schema = mongoose.Schema
ObjectId = Schema.ObjectId

THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30

ProjectInviteSchema = new Schema(
	{
		email:          String
		token:          String
		sendingUserId:  ObjectId
		projectId:      ObjectId
		privileges:     String
		createdAt:      {type: Date, default: Date.now, index: {expiresAfterSeconds: THIRTY_DAYS_IN_SECONDS}}
	},
	{
		collection: 'projectInvites'
	}
)

conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: Settings.mongo.poolSize || 10)

ProjectInvite = conn.model('ProjectInvite', ProjectInviteSchema)

mongoose.model 'ProjectInvite', ProjectInviteSchema
exports.ProjectInvite = ProjectInvite
exports.ProjectInviteSchema = ProjectInviteSchema

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

Schema = mongoose.Schema
ObjectId = Schema.ObjectId

THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30

ProjectInviteSchema = new Schema(
	{
		email:          String
		token:          String
		sendingUserId:  ObjectId
		projectId:      ObjectId
		privileges:     String
		createdAt:      {type: Date, default: Date.now, index: {expireAfterSeconds: THIRTY_DAYS_IN_SECONDS}}
	},
	{
		collection: 'projectInvites'
	}
)

conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: Settings.mongo.poolSize || 10)

ProjectInvite = conn.model('ProjectInvite', ProjectInviteSchema)

mongoose.model 'ProjectInvite', ProjectInviteSchema
exports.ProjectInvite = ProjectInvite
exports.ProjectInviteSchema = ProjectInviteSchema

Correct name of expireAfterSeconds index


TypeFactory = require '../../../../lib/type/factory'
should      = require 'should'

describe 'TypeFactory#jsType', ->

  factory = new TypeFactory

  describe 'when used with a JS class', ->
    subject = factory.jsType(Number)

    subject.should.equal(Number)

  describe 'when used with a JS class name', ->
    subject = factory.jsType('Number')

    subject.should.equal(Number)

TypeFactory = require '../../../../lib/support/factory'
should      = require 'should'

describe 'TypeFactory#jsType', ->

  factory = new TypeFactory

  describe 'when used with a JS class', ->
    subject = factory.jsType(Number)

    subject.should.equal(Number)

  describe 'when used with a JS class name', ->
    subject = factory.jsType('Number')

    subject.should.equal(Number)

Fix one require on new test

Fix one require on new test


class @PodsStore
  records: []
  all: ->
    @records
  update: (new_records) ->
    @records = @records.concat(new_records)
    @writeObjects(new_records)
  writeObjects: (pods) ->
    r = indexedDB.open('pods', 1)
    r.onupgradeneeded = (e) ->
      db = event.target.result
      if !db.objectStoreNames.contains 'pods'
        store = db.createObjectStore 'pods',
          keyPath: 'name'
        store.createIndex "stars", "stars",
          unique: false
    r.onsuccess = (e) ->
      db = r.result
      t = db.transaction 'pods', 'readwrite'
      s = t.objectStore('pods')
      for pod in pods
        r = s.put(pod)

class @PodsStore
  records: []
  all: ->
    @records
  update: (new_records) ->
    @records = @records.concat(new_records)
    @countAll (c) ->
      console.log c
    @writeObjects(new_records)
  writeObjects: (pods) ->
    @database (db) ->
      t = db.transaction 'pods', 'readwrite'
      s = t.objectStore('pods')
      for pod in pods
        r = s.put(pod)
  countAll: (callback) ->
    @database (db) ->
      t = db.transaction 'pods', 'readwrite'
      s = t.objectStore('pods')
      r = s.count()
      r.onsuccess = ->
        callback(r.result)
  database: (callback) ->
    r = indexedDB.open('pods', 1)
    r.onupgradeneeded = (e) ->
      db = event.target.result
      if !db.objectStoreNames.contains 'pods'
        store = db.createObjectStore 'pods',
          keyPath: 'name'
        store.createIndex "stars", "stars",
          unique: false
    r.onsuccess = (e) ->
      db = r.result
      callback(db)

Add index ddb record count.

Add index ddb record count.


# This module allows fetch to load local files from file://
# in node.js, electron and NW.js

if window.process?.execPath

    req = eval 'require'
    fs = req 'fs'

    class Body
        constructor: (err, @buffer) ->
            @ok = not err
            @status = ''
            @statusText = err?.message
        # NOTE: We're returning a _node_ buffer, not an arrayBuffer
        # so we can handle byteOffsets in electron...
        arrayBuffer: -> Promise.resolve @buffer
        text: -> Promise.resolve @buffer.toString()
        json: -> Promise.resolve JSON.parse @buffer.toString()

    _fetch = window._native_fetch = window._native_fetch or fetch
    window.fetch = (uri) ->
        if /^(https?:)?\/\//.test uri
            _fetch.apply window, arguments
        else
            if /^file:\/\/\/[a-z]:/.test uri
                uri = uri[8...]
            else if /^file:\/\//.test uri
                uri = uri[7...]
            new Promise (resolve, reject) ->
                fs.readFile uri, (err, data) ->
                    resolve(new Body(err, data))

# This module allows fetch to load local files from file://
# in node.js, electron and NW.js

if window.process?.execPath and process.execPath != '/'

    req = eval 'require'
    fs = req 'fs'

    class Body
        constructor: (err, @buffer) ->
            @ok = not err
            @status = ''
            @statusText = err?.message
        # NOTE: We're returning a _node_ buffer, not an arrayBuffer
        # so we can handle byteOffsets in electron...
        arrayBuffer: -> Promise.resolve @buffer
        text: -> Promise.resolve @buffer.toString()
        json: -> Promise.resolve JSON.parse @buffer.toString()

    _fetch = window._native_fetch = window._native_fetch or fetch
    window.fetch = (uri) ->
        if /^(https?:)?\/\//.test uri
            _fetch.apply window, arguments
        else
            if /^file:\/\/\/[a-z]:/.test uri
                uri = uri[8...]
            else if /^file:\/\//.test uri
                uri = uri[7...]
            new Promise (resolve, reject) ->
                fs.readFile uri, (err, data) ->
                    resolve(new Body(err, data))

Fix check of actual node.js (and not a polyfill of process).


# ***** BEGIN LICENSE BLOCK *****
# Copyright (c) 2011-2012 VMware, Inc.
#
# For the license see COPYING.
# ***** END LICENSE BLOCK *****

utils = require('./utils')
transport = require('./transport')

class EventSourceReceiver extends transport.ResponseReceiver
    protocol: "eventsource"

    doSendFrame: (payload) ->
        # Beware of leading whitespace
        data = ['data: ',
                utils.escape_selected(payload, '\r\n\x00'),
                '\r\n\r\n']
        super(data.join(''))

exports.app =
    eventsource: (req, res) ->
        if !req.headers['origin'] or req.headers['origin'] is 'null'
            origin = '*'
        else
            origin = req.headers['origin']
            res.setHeader('Access-Control-Allow-Credentials', 'true')
        res.setHeader('Content-Type', 'text/event-stream; charset=UTF-8')
        res.setHeader('Access-Control-Allow-Origin', origin)
        res.setHeader('Vary', 'Origin')
        headers = req.headers['access-control-request-headers']
        if headers
            res.setHeader('Access-Control-Allow-Headers', headers)

        res.writeHead(200)
        # Opera needs one more new line at the start.
        res.write('\r\n')

        transport.register(req, @, new EventSourceReceiver(req, res, @options))
        return true

# ***** BEGIN LICENSE BLOCK *****
# Copyright (c) 2011-2012 VMware, Inc.
#
# For the license see COPYING.
# ***** END LICENSE BLOCK *****

utils = require('./utils')
transport = require('./transport')

class EventSourceReceiver extends transport.ResponseReceiver
    protocol: "eventsource"

    doSendFrame: (payload) ->
        # Beware of leading whitespace
        data = ['data: ',
                utils.escape_selected(payload, '\r\n\x00'),
                '\r\n\r\n']
        super(data.join(''))

exports.app =
    eventsource: (req, res) ->
        if !req.headers['origin'] or req.headers['origin'] is 'null'
            origin = '*'
        else
            origin = req.headers['origin']
            res.setHeader('Access-Control-Allow-Credentials', 'true')
        res.setHeader('Content-Type', 'text/event-stream')
        res.setHeader('Access-Control-Allow-Origin', origin)
        res.setHeader('Vary', 'Origin')
        headers = req.headers['access-control-request-headers']
        if headers
            res.setHeader('Access-Control-Allow-Headers', headers)

        res.writeHead(200)
        # Opera needs one more new line at the start.
        res.write('\r\n')

        transport.register(req, @, new EventSourceReceiver(req, res, @options))
        return true

Remove charset from even source. Spec says it is always utf-8.


webpack = require 'webpack'

LANGS = ['en_gb']

module.exports =
  resolve:
    # Add automatically the following extensions to required modules
    extensions: ['', '.coffee', '.cjsx', '.js']

  plugins: [
    new webpack.ContextReplacementPlugin /moment[\\\/]locale$/, new RegExp ".[\\\/](#{LANGS.join '|'})"
  ]

  devtool: if process.env.NODE_ENV isnt 'production' then 'eval'

  module:
    loaders: [
      test: /\.cjsx$/
      loader: 'coffee!cjsx'
    ,
      test: /\.coffee$/
      loader: 'coffee'
    ,
      test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/
      loader: 'file'
    ,
      test: /\.css$/
      loader: 'style!css'
    ,
      test: /\.sass$/
      loader: 'style!css!sass?indentedSyntax'
    ]

webpack = require 'webpack'

LANGS = ['en_gb']

module.exports =
  resolve:
    # Add automatically the following extensions to required modules
    extensions: ['', '.coffee', '.cjsx', '.js']

  plugins: [
    new webpack.ContextReplacementPlugin /moment[\\\/]locale$/, new RegExp ".[\\\/](#{LANGS.join '|'})"
  ]

  ## devtool: if process.env.NODE_ENV isnt 'production' then 'eval'

  module:
    loaders: [
      test: /\.cjsx$/
      loader: 'coffee!cjsx'
    ,
      test: /\.coffee$/
      loader: 'coffee'
    ,
      test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/
      loader: 'file'
    ,
      test: /\.css$/
      loader: 'style!css'
    ,
      test: /\.sass$/
      loader: 'style!css!sass?indentedSyntax'
    ]

Remove Webpack devtool configuration (not allowed for Chrome extensions)


Spine = require('spine')

class Actions extends Spine.Controller

  elements:
    '.deal': 'deal_button'
    '.continue': 'continue_button'
    '.surrender': 'surrender_button'
    '.bet': 'bet_buttons'
    '.action': 'action_buttons'

  events:
    'click .deal': 'deal'

  constructor: ->
    super
    Spine.bind 'result', @finished
    @setup()

  setup: =>
    show @bet_buttons
    hide @action_buttons
    hide @continue_button

  deal: =>
    hide @bet_buttons
    hide @deal_button
    show @action_buttons

  finished: =>
    disable @action_buttons
    show @continue_button

  # Private

  hide = (selection) ->
    selection.hide()

  show = (selection) ->
    enable selection.show()

  disable = (selection) ->
    selection.attr('disabled', true)

  enable = (selection) ->
    selection.removeAttr('disabled')

module.exports = Actions

Spine = require('spine')

class Actions extends Spine.Controller

  elements:
    '.deal': 'deal_button'
    '.continue': 'continue_button'
    '.surrender': 'surrender_button'
    '.bet': 'bet_buttons'
    '.action': 'action_buttons'

  events:
    'click .deal': 'deal'

  constructor: ->
    super
    Spine.bind 'result', @finished
    @setup()

  setup: =>
    show @bet_buttons
    hide @action_buttons
    hide @continue_button

  deal: =>
    hide @bet_buttons
    hide @deal_button
    show @action_buttons

  finished: =>
    hide @action_buttons
    show @continue_button

  # Private

  hide = (selection) ->
    selection.hide()

  show = (selection) ->
    enable selection.show()

  disable = (selection) ->
    selection.attr('disabled', true)

  enable = (selection) ->
    selection.removeAttr('disabled')

module.exports = Actions

Hide the action buttons instead of just disabling them


define [
  'spine'
  'models/moves_path'
  'models/simplifies_segments'
  'models/link_shape'
  'spine.relation'
], (Spine, MovesPath, SimplifiesSegments, LinkShape) ->

  class Link extends Spine.Model
    @configure "Link", "sourceId", "targetId", "segments", "shape"
    @belongsTo 'drawing', 'models/drawing'

    # TODO duplication with Node
    constructor: (attributes) ->
      super
      @k = v for k,v of attributes
      @updateSegments(attributes.segments) if attributes

    select: ->
      @drawing().select(@)

    updateSegments: (segments) =>
      @segments = new SimplifiesSegments().for(segments)

    reconnectTo: (nodeId, offset) =>
      mover = new MovesPath(@toPath(), offset)
      mover.moveStart() if nodeId is @sourceId
      mover.moveEnd() if nodeId is @targetId
      @updateSegments(mover.finalise())
      @save()

    paperId: =>
      "link" + @id

    toPath: =>
      s = LinkShape.find(@shape)
      path = s.draw(@toSegments())
      path.name = @paperId()
      path

    select: (layer) =>
      layer.children[@paperId()].selected = true

    destroy: (options = {}) =>
      destroyed = super(options)
      memento =
        sourceId: destroyed.sourceId
        targetId: destroyed.targetId
        segments: destroyed.segments
        shape: destroyed.shape

    toSegments: =>
      for s in @segments
        new paper.Segment(s.point, s.handleIn, s.handleOut)

define [
  'spine'
  'models/moves_path'
  'models/link_shape'
  'spine.relation'
], (Spine, MovesPath, LinkShape) ->

  class Link extends Spine.Model
    @configure "Link", "sourceId", "targetId", "segments", "shape"
    @belongsTo 'drawing', 'models/drawing'

    # TODO duplication with Node
    constructor: (attributes) ->
      super
      @k = v for k,v of attributes

    select: ->
      @drawing().select(@)

    reconnectTo: (nodeId, offset) =>
      mover = new MovesPath(@toPath(), offset)
      mover.moveStart() if nodeId is @sourceId
      mover.moveEnd() if nodeId is @targetId
      @segments = mover.finalise()
      @save()

    paperId: =>
      "link" + @id

    toPath: =>
      s = LinkShape.find(@shape)
      path = s.draw(@segments)
      path.name = @paperId()
      path

    select: (layer) =>
      layer.children[@paperId()].selected = true

    destroy: (options = {}) =>
      destroyed = super(options)
      memento =
        sourceId: destroyed.sourceId
        targetId: destroyed.targetId
        segments: destroyed.segments
        shape: destroyed.shape

Remove dependency on simplifies segments, which is no longer needed in Paper 0.99


{Subscriber} = require 'emissary'
{$, View} = require 'atom-space-pen-views'

module.exports =
  class StatusView extends View
    Subscriber.includeInto(this)

    @content = (params) ->
      @div class: 'git-plus overlay from-bottom', =>
        @div class: "#{params.type} message", params.message

    initialize: ->
      @subscribe $(window), 'core:cancel', => @detach()
      atom.workspace.addBottomPanel(item: this)
      setTimeout =>
        @detach()
      , atom.config.get('git-plus.messageTimeout') * 1000

    detach: ->
      super
      @unsubscribe()

{Subscriber} = require 'emissary'
{$, View} = require 'atom-space-pen-views'

module.exports =
  class StatusView extends View
    Subscriber.includeInto(this)

    @content = (params) ->
      @div class: 'git-plus overlay from-bottom', =>
        @div class: "#{params.type} message", params.message

    initialize: ->
      @subscribe $(window), 'core:cancel', => @destroy()
      @panel ?= atom.workspace.addBottomPanel(item: this)
      setTimeout =>
        @destroy()
      , atom.config.get('git-plus.messageTimeout') * 1000

    destroy: ->
      @panel.destroy()
      @unsubscribe()

Fix destroy of status view bottom panel

Fix destroy of status view bottom panel


items = require("../build/liblol.js").items

describe "items", ->

  describe "find", ->

    it "should return all items when nothing is passed to it", ->
      items.find().should.include item for item in items.list

    it "should throw an error if the filter isn't a function", ->
      try
        items.find "blah"
      catch e
        e.message.should.equal "Filter is not a function!"

    it "should return a blank array if the filter is never true", ->
      items.find(-> false).should.eql []

    it "should throw an error if the comparator isn't a function", ->
      try
        items.find (-> false), "not a func"
      catch e
        e.message.should.equal "Comparator is not a function!"

    it "should sort if a comparator is given", ->
      # Put a filter to not have a huge eql
      filter = (item) -> item.name.substring(0, 1) is "A"
      comparator = (a, b) -> a.ap - b.ap

      # Ensure the sort is the same as the Array.sort
      items.find(filter).sort(comparator).should.eql items.find filter, comparator

    describe "filters", ->

      describe "withStats", ->

        it "should not include items without the stat", ->
          items.find(items.filters.withStats(["ap"])).should.not.include items.list["B. F. Sword"]

        it "should include items with the stat", ->
          items.find(items.filters.withStats(["ap"])).should.include items.list["Needlessly Large Rod"]

        it "should include items with multiple stats", ->
          items.find(items.filters.withStats(["ap", "ad"])).should.include items.list["Hextech Gunblade"]

        it "should include items with a stat in a passive", ->
          items.find(items.filters.withStats(["hp5"])).should.include items.list["Warmog's Armor"]

Add unit tests for withStats filter

Add unit tests for withStats filter

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>


# See https://atom.io/docs/latest/creating-a-package#menus for more details
'context-menu':
  '.overlayer':
      'Enable decoration-example': 'decoration-example:toggle'

'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'decoration-example'
      'submenu': [
        { 'label': 'Toggle', 'command': 'decoration-example:toggle' }
      ]
    ]
  }
]

# See https://atom.io/docs/latest/creating-a-package#menus for more details
'context-menu':
  'atom-text-editor, .overlayer': [
    {label: 'Enable decoration-example', command: 'decoration-example:toggle'}
  ]

'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'decoration-example'
      'submenu': [
        { 'label': 'Toggle', 'command': 'decoration-example:toggle' }
      ]
    ]
  }
]

Use new CSON format for menus

Use new CSON format for menus


module.exports = (grunt) ->
	grunt.initConfig
		clean:
			package: ['lib']
		coffee:
			options:
				bare: true
			files:
				expand: true
				cwd: 'src'
				src: ['**/*.coffee']
				dest: 'lib'
				ext: '.js'

	grunt.loadNpmTasks 'grunt-contrib-clean'
	grunt.loadNpmTasks 'grunt-contrib-coffee'

	grunt.registerTask 'default', []

module.exports = (grunt) ->
	grunt.initConfig
		clean:
			package: ['lib']
		coffee:
			options:
				bare: true
			files:
				expand: true
				cwd: 'src'
				src: ['**/*.coffee']
				dest: 'lib'
				ext: '.js'

	grunt.loadNpmTasks 'grunt-contrib-clean'
	grunt.loadNpmTasks 'grunt-contrib-coffee'

	grunt.registerTask 'build', ['clean:package', 'coffee']
	grunt.registerTask 'default', []

Add Grunt task alias build

Add Grunt task alias build


class window.ClientController
  constructor: (@annotatedSiteEnvoy) ->
  showFact: (fact_id) ->
    @_renderDiscussion new Fact id: fact_id

  showNewFact: (params={}) =>
    fact = new Fact
      displaystring: params.displaystring
      url: params.url
      fact_title: params.fact_title

    if Factlink.Global.signed_in
      fact.save {},
        success: =>
          @annotatedSiteEnvoy 'highlightNewFactlink', params.displaystring, fact.id
          FactlinkApp.NotificationCenter.success "Added #{Factlink.Global.t.factlink}.",
            'Undo', -> fact.destroy()

          @_renderDiscussion fact
          Backbone.history.navigate "/client/facts/#{fact.id}", trigger: false
    else
      view = new NewFactLoginView model: fact
      view.on 'render', => @annotatedSiteEnvoy 'openModalOverlay'

      clientModal = new DiscussionModalContainer
      FactlinkApp.discussionModalRegion.show clientModal
      clientModal.mainRegion.show view

  _renderDiscussion: (fact) ->
    fact.on 'destroy', => @annotatedSiteEnvoy 'closeModal_deleteFactlink', fact.id

    fact.fetch
      success: =>
        newClientModal = new DiscussionModalContainer
        FactlinkApp.discussionModalRegion.show newClientModal
        view = new DiscussionView model: fact
        view.on 'render', => @annotatedSiteEnvoy 'openModalOverlay'
        newClientModal.mainRegion.show view

class window.ClientController
  constructor: (@annotatedSiteEnvoy) ->
  showFact: (fact_id) ->
    @_renderDiscussion new Fact id: fact_id

  showNewFact: (params={}) =>
    fact = new Fact
      displaystring: params.displaystring
      url: params.url
      fact_title: params.fact_title

    if Factlink.Global.signed_in
      fact.save {},
        success: =>
          @annotatedSiteEnvoy 'highlightNewFactlink', params.displaystring, fact.id
          FactlinkApp.NotificationCenter.success "Added #{Factlink.Global.t.factlink}.",
            'Undo', -> fact.destroy()

          @_renderDiscussion fact
          Backbone.history.navigate "/client/facts/#{fact.id}", trigger: false
    else
      view = new NewFactLoginView model: fact
      view.on 'render', => @annotatedSiteEnvoy 'openModalOverlay'

      clientModal = new DiscussionModalContainer
      FactlinkApp.discussionModalRegion.show clientModal
      clientModal.mainRegion.show view

  _renderDiscussion: (fact) ->
    fact.on 'destroy', => @annotatedSiteEnvoy 'deleteFactlink', fact.id

    fact.fetch
      success: =>
        newClientModal = new DiscussionModalContainer
        FactlinkApp.discussionModalRegion.show newClientModal
        view = new DiscussionView model: fact
        view.on 'render', => @annotatedSiteEnvoy 'openModalOverlay'
        newClientModal.mainRegion.show view

Use just "deleteFactlink" event, as we also fire another event for closing the modal already


angular.module('loomioApp').factory 'KeyEventService', ($rootScope) ->
  new class KeyEventService

    keyboardShortcuts:
      73:  'pressedI'
      71:  'pressedG'
      84:  'pressedT'
      27:  'pressedEsc'
      13:  'pressedEnter'
      191: 'pressedSlash'
      38:  'pressedUpArrow'
      40:  'pressedDownArrow'

    broadcast: (event) ->
      key = @keyboardShortcuts[event.which]
      if key == 'pressedEnter' or (key and !event.ctrlKey and !event.metaKey)
        $rootScope.$broadcast key, event, angular.element(document.activeElement)[0]

    registerKeyEvent: (scope, eventCode, execute, shouldExecute) ->
      shouldExecute = shouldExecute or @defaultShouldExecute
      scope.$on eventCode, (angularEvent, originalEvent, active) ->
        if shouldExecute(active, originalEvent)
          angularEvent.preventDefault() and originalEvent.preventDefault()
          execute(active)

    defaultShouldExecute: (active = {}, event = {}) ->
      !event.ctrlKey and !event.altKey and !_.contains(['INPUT', 'TEXTAREA', 'SELECT'], active.nodeName)

    submitOnEnter: (scope) ->
      @registerKeyEvent scope, 'pressedEnter', scope.submit, (active, event) =>
        (event.ctrlKey or event.metaKey) and
        angular.element(active).scope() == scope and
        _.contains(active.classList, 'lmo-primary-form-input')

angular.module('loomioApp').factory 'KeyEventService', ($rootScope) ->
  new class KeyEventService

    keyboardShortcuts:
      73:  'pressedI'
      71:  'pressedG'
      84:  'pressedT'
      27:  'pressedEsc'
      13:  'pressedEnter'
      191: 'pressedSlash'
      38:  'pressedUpArrow'
      40:  'pressedDownArrow'

    broadcast: (event) ->
      key = @keyboardShortcuts[event.which]
      if key == 'pressedEnter' or (key and !event.ctrlKey and !event.metaKey)
        $rootScope.$broadcast key, event, angular.element(document.activeElement)[0]

    registerKeyEvent: (scope, eventCode, execute, shouldExecute) ->
      shouldExecute = shouldExecute or @defaultShouldExecute
      scope.$on eventCode, (angularEvent, originalEvent, active) ->
        if shouldExecute(active, originalEvent)
          angularEvent.preventDefault() and originalEvent.preventDefault()
          execute(active)

    defaultShouldExecute: (active = {}, event = {}) ->
      !event.ctrlKey and !event.altKey and !_.contains(['INPUT', 'TEXTAREA', 'SELECT'], active.nodeName)

    submitOnEnter: (scope) ->
      @registerKeyEvent scope, 'pressedEnter', scope.submit, (active, event) =>
        (event.ctrlKey or event.metaKey) and
        angular.element(active).scope().$$isolateBindings == scope.$$isolateBindings and
        _.contains(active.classList, 'lmo-primary-form-input')

Fix cmd + enter submit

Fix cmd + enter submit


noflo = require 'noflo'
require 'framerjs'

createLayer = (properties) ->
  return new Layer properties

exports.getComponent = ->
  component = new noflo.Component
  component.description = 'Set layer properties.'
  # component.icon = 'picture'

  # Add input ports
  component.inPorts.add 'in',
    datatype: 'object'
    description: 'The layer to set properties on.'
    process: (event, payload) ->
      # What to do when port receives a packet
      return unless event is 'data'
      component.layer = payload
      component.outPorts.layer.send payload

  component.inPorts.add 'properties',
    datatype: 'object'
    description: 'Properties to set on the layer.'
    process: (event, payload) ->
      # What to do when port receives a packet
      return unless event is 'data'
      component.layer[key] = value for key, value of payload
      component.outPorts.layer.send component.layer

  # Add output ports
  component.outPorts.add 'layer',
    datatype: 'object'
    description: 'The layer object passed as input.'

  component

noflo = require 'noflo'
require 'framerjs'

createLayer = (properties) ->
  return new Layer properties

exports.getComponent = ->
  component = new noflo.Component
  component.description = 'Set layer properties.'
  component.icon = 'cogs'

  # Add input ports
  component.inPorts.add 'in',
    datatype: 'object'
    description: 'The layer to set properties on.'
    process: (event, payload) ->
      # What to do when port receives a packet
      return unless event is 'data'
      component.layer = payload
      component.outPorts.layer.send payload

  component.inPorts.add 'properties',
    datatype: 'object'
    description: 'Properties to set on the layer.'
    process: (event, payload) ->
      # What to do when port receives a packet
      return unless event is 'data'
      component.layer[key] = value for key, value of payload
      component.outPorts.layer.send component.layer

  # Add output ports
  component.outPorts.add 'layer',
    datatype: 'object'
    description: 'The layer object passed as input.'

  component

Configure icon for SetLayerProperty component.


# Facebook config
exports.facebook =
	appId: null
	secretKey: null

# Meetup config
exports.meetup = null # API key as string

#Eventbrite config
exports.eventbrite =
	appKey: null

exports.elasticsearch =
	host: 'localhost:9200'
	log: 'trace'
	apiVersion: '1.5'

exports.geo =
	center: # Insert lat and lon coordinates for your area of interest
		lat: 0.0
		lon: 0.0
	maxDistKilometers: 100 # Use something you feel is sensible

Add geoconfig to config example

Add geoconfig to config example

Add example of geo module config to config example file.


window.Cosmo =
  version: '0.1.0'

# more
# All apps should be an extended instance of this class
class Cosmo.Router
  # app regions, assumes main content goes in a div#container
  regions:
    container: $('#container')

  initialize: ->

  # do initialization stuff and then start app routing
  # TODO: Do some url management
  start: ->
    @initialize()
    @go 'home'
    this

  # Takes an object with names and jquery selectors
  addRegions: (regions) ->
    for key, value of regions
      @regions[key] = $(value)
    this

  # set the value
  # TODO: Return value or this? (to allow chaining)
  set: (key, val) ->
    if val?
      this[key] = val
    else
      this[key] = null
    this

  get: (key) -> this[key]

  # main routing function
  go: (page, args...) ->
    if this[page]?
      this[page]?(args[0], args[1])
    else
      @home()

  render: (view) -> @regions.container.html view

window.Cosmo =
  version: '0.1.0'

# All apps should be an extended instance of this class
class Cosmo.Router
  # app regions, assumes main content goes in a div#container
  regions:
    container: $('#container')

  initialize: ->

  # do initialization stuff and then start app routing
  # TODO: Do some url management
  start: ->
    @initialize()
    @go 'home'
    this

  # Takes an object with names and jquery selectors
  addRegions: (regions) ->
    for key, value of regions
      @regions[key] = $(value)
    this

  # set the value
  # TODO: Return value or this? (to allow chaining)
  set: (key, val) ->
    if val?
      this[key] = val
    else
      this[key] = null
    this

  get: (key) -> this[key]

  # main routing function
  go: (page, args...) ->
    if this[page]?
      this[page]?(args[0], args[1])
    else
      @home()

  render: (view) -> @regions.container.html view

Remove comment in branch test

Remove comment in branch test


ETahi.ApplicationController = Ember.Controller.extend
  currentUser:(->
    userId = Tahi.currentUser?.id.toString()
    @store.getById('user', userId)
  ).property().volatile()

  connectToES:(->
    return unless Tahi.currentUser?.id
    store = @store
    params =
      url: '/event_stream'
      method: 'GET'
      success:(data)->
        source = new EventSource(data.url)
        data.eventNames.forEach (eventName)->
          source.addEventListener eventName, (msg)->
            esData = JSON.parse(msg.data)
            Ember.run ->
              type = esData.type.replace(/.+::/, '')
              delete esData.type
              store.pushPayload(type, esData)

    Ember.$.ajax(params)
  ).on('init')

  overlayBackground: Ember.computed.defaultTo('defaultBackground')

  overlayRedirect: null

  defaultBackground: 'overlay_background'

ETahi.ApplicationController = Ember.Controller.extend
  currentUser:(->
    userId = Tahi.currentUser?.id.toString()
    @store.getById('user', userId)
  ).property().volatile()

  connectToES:(->
    return unless Tahi.currentUser?.id
    store = @store
    params =
      url: '/event_stream'
      method: 'GET'
      success:(data)->
        source = new EventSource(data.url)
        data.eventNames.forEach (eventName)->
          source.addEventListener eventName, (msg)->
            esData = JSON.parse(msg.data)
            Ember.run ->
              type = esData.type.replace(/.+::/, '')
              delete esData.type
              store.pushPayload(type, esData)

              if esData.task
                store.find('task', esData.task.id).then (task)->
                  # ember.js bug:  need to tell phase about any new tasks
                  task.get('phase').get('tasks').pushObject(task)

    Ember.$.ajax(params)
  ).on('init')

  overlayBackground: Ember.computed.defaultTo('defaultBackground')

  overlayRedirect: null

  defaultBackground: 'overlay_background'

Handle new addhoc Tasks to existing Phase


Command = require 'command-interpreter/command'
Range = require 'range'

module.exports =
class SelectAllMatches extends Command
  @regex: null

  constructor: (pattern) ->
    @regex = @regexForPattern(pattern)

  execute: (editor) ->
    rangesToSelect = []
    for selection in editor.getSelections()
      selectedText = selection.getText()
      selectionStartIndex = editor.buffer.characterIndexForPosition(selection.getBufferRange().start)
      for range in @findMatchingRanges(editor, selectedText, selectionStartIndex)
        rangesToSelect.push(range)

    editor.clearSelections()
    editor.addSelectionForBufferRange(range) for range in rangesToSelect

  findMatchingRanges: (editor, text, startIndex) ->
    return [] unless match = text.match(@regex)

    matchStartIndex = startIndex + match.index
    matchEndIndex = matchStartIndex + match[0].length

    buffer = editor.buffer
    startPosition = buffer.positionForCharacterIndex(matchStartIndex)
    endPosition = buffer.positionForCharacterIndex(matchEndIndex)
    range = new Range(startPosition, endPosition)

    text = text[(match.index + match[0].length)..]
    startIndex = matchEndIndex
    [range].concat(@findMatchingRanges(editor, text, startIndex))

Command = require 'command-interpreter/command'
Range = require 'range'

module.exports =
class SelectAllMatches extends Command
  regex: null

  constructor: (pattern) ->
    @regex = new RegExp(pattern, 'g')

  execute: (editor) ->
    rangesToSelect = []
    for selection in editor.getSelections()
      editor.buffer.traverseRegexMatchesInRange @regex, selection.getBufferRange(), (match, range) ->
        rangesToSelect.push(range)

    editor.clearSelections()
    editor.addSelectionForBufferRange(range) for range in rangesToSelect

Implement select all matches command (x) with Buffer.traverseRegexMatchesInRange


#= require_tree ./vendors
#= require_tree ./written/core
#= require_tree ./written/extensions
#= require_tree ./written/parsers

#= require_tree ./vendors
#= require_tree ./written/core
#= require_tree ./written/parsers

Remove sprocket directive for extensions


'menu': [
  'label': 'Edit'
  'submenu': [
    'label': 'Select Encoding'
    'command': 'encoding-selector:show'
  ]
]

'context-menu':
  '.overlayer': [
    'label': 'Change Encoding'
    'command': 'encoding-selector:show'
  ]

Add change item to editor context menu

Add change item to editor context menu


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

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

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

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

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

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

    this

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

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

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

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

  markAsRead: ->
    @collection.markRead()
    @state.set('unread_count', 0)

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

    this

Set notification events in backbone event hash


StatsTracker = require './stats-tracker'

module.exports =
  stats: null
  editorStatsView: null

  activate: ->
    @stats = new StatsTracker()
    rootView.command 'editor-stats:toggle', => @createView().toggle(@stats)

  deactivate: ->
    @editorStatsView = null
    @stats = null

  createView: ->
    unless @editorStatsView
      EditorStatsView  = require 'editor-stats/lib/editor-stats-view'
      @editorStatsView = new EditorStatsView()
    @editorStatsView

StatsTracker = require './stats-tracker'

module.exports =
  stats: null
  editorStatsView: null

  activate: ->
    @stats = new StatsTracker()
    rootView.command 'editor-stats:toggle', => @createView().toggle(@stats)

  deactivate: ->
    @editorStatsView = null
    @stats = null

  createView: ->
    unless @editorStatsView
      EditorStatsView  = require './editor-stats-view'
      @editorStatsView = new EditorStatsView()
    @editorStatsView

Use a relative require path

Use a relative require path


a = DS.attr
ETahi.AdminJournal = DS.Model.extend
  logoUrl: a('string')
  name: a('string')
  paperTypes: a()
  taskTypes: a()
  manuscriptManagerTemplates: a('manuscriptManagerTemplate')
  roles: DS.hasMany('role')
  epubCoverUrl: a('string')
  epubCoverFileName: a('string')
  epubCss: a('string')
  pdfCss: a('string')
  manuscriptCss: a('string')
  description: a('string')
  paperCount: a('number')
  createdAt: a('date')

Add createdAt to admin_journal model

Add createdAt to admin_journal model


{View} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'

module.exports =
class WrapGuideView extends View
  @activate: ->
    rootView.eachEditor (editor) =>
      @appendToEditorPane(rootView, editor) if editor.attached

  @appendToEditorPane: (rootView, editor) ->
    if underlayer = editor.find('.underlayer')
      underlayer.append(new WrapGuideView(editor))

  @content: ->
    @div class: 'wrap-guide'

  defaultColumn: 80

  initialize: (@editor) =>
    @observeConfig 'editor.fontSize', => @updateGuide()
    @subscribe @editor, 'editor:path-changed', => @updateGuide()
    @subscribe @editor, 'editor:min-width-changed', => @updateGuide()
    @subscribe $(window), 'resize', => @updateGuide()

  getGuideColumn: (path) ->
    customColumns = config.get('wrapGuide.columns')
    return @defaultColumn unless _.isArray(customColumns)
    for customColumn in customColumns
      continue unless _.isObject(customColumn)
      pattern = customColumn['pattern']
      continue unless pattern
      return parseInt(customColumn['column']) if new RegExp(pattern).test(path)
    @defaultColumn

  updateGuide: ->
    column = @getGuideColumn(@editor.getPath(), @defaultColumn)
    if column > 0
      columnWidth = @editor.charWidth * column
      if columnWidth < @editor.layerMinWidth or columnWidth < @editor.width()
        @css('left', columnWidth).show()
      else
        @hide()
    else
      @hide()

{View} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'

module.exports =
class WrapGuideView extends View
  @activate: ->
    rootView.eachEditor (editor) =>
      editor.underlayer.append(new WrapGuideView(editor)) if editor.attached

  @content: ->
    @div class: 'wrap-guide'

  defaultColumn: 80

  initialize: (@editor) =>
    @observeConfig 'editor.fontSize', => @updateGuide()
    @subscribe @editor, 'editor:path-changed', => @updateGuide()
    @subscribe @editor, 'editor:min-width-changed', => @updateGuide()
    @subscribe $(window), 'resize', => @updateGuide()

  getGuideColumn: (path) ->
    customColumns = config.get('wrapGuide.columns')
    return @defaultColumn unless _.isArray(customColumns)
    for customColumn in customColumns
      continue unless _.isObject(customColumn)
      pattern = customColumn['pattern']
      continue unless pattern
      return parseInt(customColumn['column']) if new RegExp(pattern).test(path)
    @defaultColumn

  updateGuide: ->
    column = @getGuideColumn(@editor.getPath(), @defaultColumn)
    if column > 0
      columnWidth = @editor.charWidth * column
      if columnWidth < @editor.layerMinWidth or columnWidth < @editor.width()
        @css('left', columnWidth).show()
      else
        @hide()
    else
      @hide()

Use underlayer property on editor

Use underlayer property on editor

Removes unneeded call to find


define [
  'jquery'
  'underscore'
  'jade.templates'
  'mixen'
  'mixens/BaseViewMixen'
  'mixens/ModelViewMixen'
  'views/DonationFormView'
  'vex'
], ($, _, jade, Mixen, BaseView, ModelView, DonationFormView, vex) ->
  class IndexStats extends Mixen(ModelView, BaseView)
    template: jade.indexStats
    events:
      'click .donate-button': 'donate'

    render: =>
      loadingContext = @getRenderContext()

      percent = (((@model.get('amountRaised') / 100) / 100000) * 100) or 0
      percentWidth = if (percent > 100) then 100 else percent

      context =
        percentageWidth: percentWidth
        percentage: percent
      _.extend context, loadingContext
      _.extend context, @model.toJSON()

      @$el.html @template context
      @

    donate: (event) =>
      donationView = new DonationFormView
        parent: @

      vex.defaultOptions.className = 'vex-theme-default'
      $vexContent = vex.open
        content: donationView.render().$el
        contentClassName: 'narrow padding-less'
        overlayClosesOnClick: false
        afterOpen: ($vexContent) ->
          $vexContent.append.$el

      @donateVexId = $vexContent.data().vex.id

    closeDonateVex: =>
      vex.close(@donateVexId)

define [
  'jquery'
  'underscore'
  'jade.templates'
  'mixen'
  'mixens/BaseViewMixen'
  'mixens/ModelViewMixen'
  'views/DonationFormView'
  'vex'
], ($, _, jade, Mixen, BaseView, ModelView, DonationFormView, vex) ->
  class IndexStats extends Mixen(ModelView, BaseView)
    template: jade.indexStats
    events:
      'click .donate-button': 'donate'
    target: 50000 # targeted amount in euros

    render: =>
      loadingContext = @getRenderContext()

      percent = (((@model.get('amountRaised') / 100) / @target) * 100) or 0
      percentWidth = if (percent > 100) then 100 else percent

      context =
        percentageWidth: percentWidth
        percentage: percent
      _.extend context, loadingContext
      _.extend context, @model.toJSON()

      @$el.html @template context
      @

    donate: (event) =>
      donationView = new DonationFormView
        parent: @

      vex.defaultOptions.className = 'vex-theme-default'
      $vexContent = vex.open
        content: donationView.render().$el
        contentClassName: 'narrow padding-less'
        overlayClosesOnClick: false
        afterOpen: ($vexContent) ->
          $vexContent.append.$el

      @donateVexId = $vexContent.data().vex.id

    closeDonateVex: =>
      vex.close(@donateVexId)

Fix the target progress bar

Fix the target progress bar


VoteButton = require './VoteButton.coffee'

init = (el)->
  $feeds = $(el)
  $btnReload = $('#btn-reload-feeds')
  $tipLoading = $('#feeds-loading-tip')
  $tipLoaded = $('#feeds-loaded-tip')
  pages = parseInt $feeds.data('pages')
  loading = false
  page = 1

  load = ()->
    loading = true
    $.ajax
      data:
        page: page
        target: el
      success: (html)->
        $tipLoading.before html
        VoteButton.init el
        if page >= pages
          $tipLoading.hide()
          $tipLoaded.show()
        else
          $tipLoaded.hide()
        setTimeout ()->
          loading = false
        , 1000
      error: (res)->
        $tipLoading.addClass 'has-error'

  checkLoad = ()->
    $last = $feeds.find('.feed').last()
    scrollTop = $(window).scrollTop() + $(window).height()
    if scrollTop < $last.offset().top - $last.height() * 2
      return
    if loading or page >= pages
      return
    page += 1
    load()

  if isNaN pages
    return console.error 'invalid data-pages attribute'
  VoteButton.init el
  $(window).on 'scroll', checkLoad
  $btnReload.on 'click', ()->
    $tipLoading.removeClass 'has-error'
    load()
  checkLoad()

module.exports = init: init

VoteButton = require './VoteButton.coffee'

init = (el)->
  $feeds = $(el)
  $btnReload = $('#btn-reload-feeds')
  $tipLoading = $('#feeds-loading-tip')
  $tipLoaded = $('#feeds-loaded-tip')
  pages = parseInt $feeds.data('pages')
  loading = false
  page = 1

  load = ()->
    loading = true
    $.ajax
      data:
        page: page
        target: el
      success: (html)->
        $tipLoading.before html
        VoteButton.init el
        if page >= pages
          $tipLoading.hide()
          $tipLoaded.show()
        else
          $tipLoaded.hide()
        setTimeout ()->
          loading = false
        , 500
      error: (res)->
        $tipLoading.addClass 'has-error'

  checkLoad = ()->
    $last = $feeds.find('.feed').last()
    scrollTop = $(window).scrollTop() + $(window).height()
    if scrollTop < $last.offset().top - $last.height()
      return
    if loading or page >= pages
      return
    page += 1
    load()

  if isNaN pages
    return console.error 'invalid data-pages attribute'
  VoteButton.init el
  $(window).on 'scroll', checkLoad
  $btnReload.on 'click', ()->
    $tipLoading.removeClass 'has-error'
    load()
  checkLoad()

module.exports = init: init

Update feed list load check

Update feed list load check


packages: [
  "autocomplete-emojis"
  "editorconfig"
  "file-icons"
  "highlight-selected"
  "linter"
  "linter-coffeelint"
  "linter-csslint"
  "linter-flake8"
  "linter-htmlhint"
  "linter-js-yaml"
  "linter-jshint"
  "linter-jsonlint"
  "linter-less"
  "linter-php"
  "linter-scss-lint"
  "linter-shellcheck"
  "linter-write-good"
  "linter-xmllint"
  "minimap"
  "omni-ruler"
  "package-sync"
  "pigments"
  "platformio-ide-terminal"
  "pretty-json"
  "sort-lines"
]

packages: [
  "autocomplete-emojis"
  "editorconfig"
  "file-icons"
  "highlight-selected"
  "linter"
  "linter-coffeelint"
  "linter-csslint"
  "linter-eslint"
  "linter-flake8"
  "linter-htmlhint"
  "linter-js-yaml"
  "linter-jsonlint"
  "linter-less"
  "linter-php"
  "linter-scss-lint"
  "linter-shellcheck"
  "linter-write-good"
  "linter-xmllint"
  "minimap"
  "omni-ruler"
  "package-sync"
  "pigments"
  "platformio-ide-terminal"
  "pretty-json"
  "sort-lines"
]

Replace linter-jshint with linter-eslint in Atom


_ = require 'underscore'
BufferedProcess = require 'buffered-process'

module.exports =
class LoadPathsTask
  constructor: (@callback) ->

  start: ->
    rootPath = project.getPath()
    ignoredNames = config.get('fuzzyFinder.ignoredNames') ? []
    ignoredNames = ignoredNames.concat(config.get('core.ignoredNames') ? [])
    ignoreGitIgnoredFiles =  config.get('core.hideGitIgnoredFiles')

    command = require.resolve 'nak'
    args = ['--list', rootPath]
    args.unshift('--addVCSIgnores') if config.get('nak.addVCSIgnores')
    args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
    args.unshift('--follow')

    paths = []
    exit = =>
      @callback(paths)
    stdout = (data) ->
      paths.push(_.compact(data.split('\n'))...)

    @process = new BufferedProcess({command, args, stdout, exit})

  abort: ->
    if @process?
      @process.kill()
      @process = null

_ = require 'underscore'
BufferedProcess = require 'buffered-process'

module.exports =
class LoadPathsTask
  constructor: (@callback) ->

  start: ->
    rootPath = project.getPath()
    ignoredNames = config.get('fuzzyFinder.ignoredNames') ? []
    ignoredNames = ignoredNames.concat(config.get('core.ignoredNames') ? [])

    command = require.resolve 'nak'
    args = ['--list', rootPath]
    args.unshift('--addVCSIgnores') if config.get('fuzzyFinder.hideVcsIgnoredPaths')
    args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
    args.unshift('--follow')

    paths = []
    exit = =>
      @callback(paths)
    stdout = (data) ->
      paths.push(_.compact(data.split('\n'))...)

    @process = new BufferedProcess({command, args, stdout, exit})

  abort: ->
    if @process?
      @process.kill()
      @process = null

Rename VCS ignore config setting to fuzzyFinder.hideVcsIgnoredPaths


window.emojify = ->
  $(".js-emoji").each ->
    $(@).html(emojione.toImage($(@).text()))

$ -> emojify()

window.emojify = ->
  $(".js-emoji").each ->
    # $(@).html(emojione.toImage($(@).text()))

$ -> emojify()

Disable emoji support for now as it’s unescaping escaped html!

#919


$(document).ready ->
  opts =
    lines: 11
    length: 2
    width: 3
    radius: 9
    corners: 1
    rotate: 0
    color: '#fff'
    speed: 0.8
    trail: 48
    shadow: false
    hwaccel: true
    className: 'spinner'
    zIndex: 2e9
    top: 'auto'
    left: 'auto'

  target = document.getElementById("spinner")

  $(document).ajaxStart ->
    $("#progress").fadeIn()
    spinner = new Spinner(opts).spin(target)

  $(document).ajaxStop ->
    $("#progress").fadeOut()

$(document).ready ->
  opts =
    lines: 11
    length: 2
    width: 3
    radius: 9
    corners: 1
    rotate: 0
    color: '#fff'
    speed: 0.8
    trail: 48
    shadow: false
    hwaccel: true
    className: 'spinner'
    zIndex: 2e9
    top: 'auto'
    left: 'auto'

  target = document.getElementById("spinner")

  $(document).ajaxStart ->
    $("#progress").stop(true, true).fadeIn()
    spinner = new Spinner(opts).spin(target)

  $(document).ajaxStop ->
    $("#progress").fadeOut()

Change spinner to stop if another is started for a new ajax request

* During testing I was rapidly saving a payment to make sure there
  were no timing related issues in the javascript. One thing I
  noticed is that the spinners would queue up, and keep fading in
  and out long after the last ajax request has completed.

  I felt like a better experience would be to stop any in-progress
  spinners if a new spinner needs to start.


'use strict'

###*
 # @ngdoc service
 # @name arashike-blog.githubshareddata
 # @description
 # # githubshareddata
 # Service in the arashike-blog.
###
angular.module 'arashike-blog'
  .service 'sharedDataService', ->
    gists = []

    return {
      gists: gists
    }

'use strict'

###*
 # @ngdoc service
 # @name arashike-blog.githubshareddata
 # @description
 # # githubshareddata
 # Service in the arashike-blog.
###
angular.module 'arashike-blog'
  .service 'sharedDataService', ->
    author = {}
    gists = []

    return {
      author: author
      gists: gists
    }

Add property `author` to sharedDataService


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

FactlinkJailRoot.on 'modalOpened', ->
  document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
FactlinkJailRoot.on 'modalClosed', -> document.documentElement.removeAttribute('data-factlink-suppress-scrolling')

Implement a scrollbar width detector

Implement a scrollbar width detector


'.editor':
  'ctrl-m': 'bracket-matcher:go-to-matching-bracket'
  'ctrl-]': 'bracket-matcher:remove-brackets-from-selection'

'.platform-darwin .editor':
  'ctrl-cmd-m': 'bracket-matcher:select-inside-brackets'
  'alt-cmd-.': 'bracket-matcher:close-tag'

'.platform-linux .editor':
  'alt-ctrl-.': 'bracket-matcher:close-tag'

'.platform-win32 .editor':
  'alt-ctrl-.': 'bracket-matcher:close-tag'

'.editor':
  'ctrl-m': 'bracket-matcher:go-to-matching-bracket'
  'ctrl-]': 'bracket-matcher:remove-brackets-from-selection'
  'ctrl-backspace': 'bracket-matcher:remove-matching-brackets'

'.platform-darwin .editor':
  'ctrl-cmd-m': 'bracket-matcher:select-inside-brackets'
  'alt-cmd-.': 'bracket-matcher:close-tag'

'.platform-linux .editor':
  'alt-ctrl-.': 'bracket-matcher:close-tag'

'.platform-win32 .editor':
  'alt-ctrl-.': 'bracket-matcher:close-tag'

Add a new keymap `ctrl-backspace`

Add a new keymap `ctrl-backspace`


class App.Log
  _instance = undefined

  @log: ( module, level, args... ) ->
    if _instance == undefined
      _instance ?= new _Singleton
    _instance.log( module, level, args )

  @debug: ( module, level, args... ) ->
    if _instance == undefined
      _instance ?= new _Singleton
    _instance.log( module, level, args )

class _Singleton
  constructor: ->
    @config = {}
#      Session: true
#      ControllerForm: true

  log: ( module, level, args ) ->
    if !@config || level isnt 'debug'
      @_log( module, level, args )
    else if @config[ module ]
      @_log( module, level, args )

  _log: ( module, level, args ) ->
    return if !console
    return if !console.log
    console.log "App.#{module}(#{level})", args

class App.Log
  _instance = undefined

  @log: ( module, level, args... ) ->
    if _instance == undefined
      _instance ?= new _Singleton
    _instance.log( module, level, args )

  @debug: ( module, level, args... ) ->
    if _instance == undefined
      _instance ?= new _Singleton
    _instance.log( module, level, args )

class _Singleton
  constructor: ->
    @config = {}
#    @config['Collection'] = true
#      Session: true
#      ControllerForm: true

  log: ( module, level, args ) ->
    if !@config || level isnt 'debug'
      @_log( module, level, args )
    else if @config[ module ]
      @_log( module, level, args )

  _log: ( module, level, args ) ->
    return if !'console' in window
    return if !'log' in console
    return if !typeof console.log isnt 'function'
    console.log "App.#{module}(#{level})", args

Make console.log working in IE8.

Make console.log working in IE8.