should = require 'should'
restler = require 'restler'

setup = require './setup'

describe 'with no services or routes wired up', () ->
  beforeEach (done) ->
    {@app, @server, @url} = setup()
    done()

  afterEach (done) ->
    @server.close()
    done()

  it 'should return with a 501 status', (done) ->
    req = restler.get @url
    should.exist req

    req.on 'complete', (result, response) ->
      should.exist result, 'expected result to exist'
      should.exist response, 'expected response to exist'
      should.exist response.statusCode, 'expected statusCode to exist'

      response.statusCode.should.equal 501

      done()

should = require 'should'
restler = require 'restler'

setup = require './setup'

describe 'with no services or routes wired up', () ->
  beforeEach (done) ->
    {@app, @server, @url} = setup()
    done()

  afterEach (done) ->
    @server.close()
    done()

  it 'should return with a 501 status', (done) ->
    req = restler.get @url
    should.exist req

    req.once 'complete', (result, response) ->
      should.exist result, 'expected result to exist'
      should.exist response, 'expected response to exist'
      should.exist response.statusCode, 'expected statusCode to exist'

      response.statusCode.should.equal 501

      done()

Fix adapter code to handle unresolved services, errors


angular.module('loomioApp').directive 'lmoHref', ($window, $router, LmoUrlService) ->
  restrict: 'A'
  scope:
    path: '@lmoHref'
    model: '=lmoHrefModel'
  link: (scope, elem, attrs) ->
    route = LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
    elem.attr 'href', ''
    elem.bind 'click', ($event) ->
      if $event.metaKey
        $window.open route, '_blank'
      else
        $router.navigate route

angular.module('loomioApp').directive 'lmoHref', ($window, $router, LmoUrlService) ->
  restrict: 'A'
  scope:
    path: '@lmoHref'
    model: '=lmoHrefModel'
  link: (scope, elem, attrs) ->
    route = LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
    elem.attr 'href', ''
    elem.bind 'click', ($event) ->
      if $event.metaKey or $event.ctrlKey
        $window.open route, '_blank'
      else
        $router.navigate route

Add support for ctrl key

Add support for ctrl key


"*":
  autosave:
    enabled: true
  core:
    audioBeep: false
    closeDeletedFileTabs: true
    excludeVcsIgnoredPaths: false
    packagesWithKeymapsDisabled: []
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "one-light-syntax"
    ]
  "custom-title":
    template: "<%= fileName %><% if (projectPath) { %> - <%= projectPath %><% if (gitHead) { %> [<%= gitHead %>]<% } %><% } %>"
  editor:
    backUpBeforeSaving: true
    fontSize: 26
    invisibles: {}
    preferredLineLength: 120
    scrollPastEnd: true
    showIndentGuide: true
    softWrap: true
    softWrapAtPreferredLineLength: true
    zoomFontWhenCtrlScrolling: true
  "exception-reporting":
    userId: "23726a94-4d58-6922-8de3-2676f1bd97ca"
  pigments:
    extendAutocompleteToColorValue: true
    extendAutocompleteToVariables: true
  "release-notes":
    viewedVersion: "0.94.0"
  "tree-view":
    hideVcsIgnoredFiles: true
    squashDirectoryNames: true
  "tree-view-open-files": {}
  welcome:
    showOnStartup: false
  wordcount:
    alwaysOn: true
    noextension: true
".css.source.stylus":
  editor:
    preferredLineLength: 120

"*":
  autosave:
    enabled: true
  core:
    audioBeep: false
    closeDeletedFileTabs: true
    excludeVcsIgnoredPaths: false
    packagesWithKeymapsDisabled: []
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "one-light-syntax"
    ]
  "custom-title":
    template: "<%= fileName %><% if (projectPath) { %> - <%= projectPath %><% if (gitHead) { %> [<%= gitHead %>]<% } %><% } %>"
  editor:
    backUpBeforeSaving: true
    fontSize: 19
    invisibles: {}
    preferredLineLength: 120
    scrollPastEnd: true
    showIndentGuide: true
    softWrap: true
    softWrapAtPreferredLineLength: true
    zoomFontWhenCtrlScrolling: true
  "exception-reporting":
    userId: "23726a94-4d58-6922-8de3-2676f1bd97ca"
  pigments:
    extendAutocompleteToColorValue: true
    extendAutocompleteToVariables: true
  "release-notes":
    viewedVersion: "0.94.0"
  "tree-view":
    hideVcsIgnoredFiles: true
    squashDirectoryNames: true
  "tree-view-open-files": {}
  welcome:
    showOnStartup: false
  wordcount:
    alwaysOn: true
    noextension: true
".css.source.stylus":
  editor:
    preferredLineLength: 120

Adjust font size of editor

Adjust font size of editor


class IDE.PreviewPane extends IDE.Pane

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

    options.cssClass = KD.utils.curry 'preview-pane', options.cssClass
    options.paneType = 'preview'

    super options, data

    viewerOptions =
      delegate    : this
      params      :
        path      : @getOptions().url

    delete viewerOptions.params.path  unless viewerOptions.params.path

    @addSubView @previewer = new PreviewerView viewerOptions

    @previewer.on 'ViewerLocationChanged', (newLocation) =>
      @setOption 'url', newLocation
      @emit 'LocationChanged', newLocation

Update preview pane url and emit location changed event.


$(document).on 'ready page:load', ->

  $("a.popover").click (event) ->
    event.preventDefault()
    popover_src = $(this).children("img").attr("data-src")
    popover_caption = $(this).children("img").attr("alt")
    popover_width = $(this).children("img").attr("data-width")
    popover_height = $(this).children("img").attr("data-height")
    $("div#popover img").attr "src", popover_src
    $("div#popover img").attr "alt", popover_caption
    $("div#popover img").attr "width", popover_width
    $("div#popover img").attr "height", popover_height
    $("div#popover").fadeIn "fast"

  $("div#popover button").click ->
    event.preventDefault()
    $("div#popover").fadeOut "fast"

$(document).on 'ready page:load', ->

  $("a.popover").click (event) ->
    event.preventDefault()
    popover_src = $(this).children("img").attr("data-src")
    popover_caption = $(this).children("img").attr("alt")
    popover_width = $(this).children("img").attr("data-width")
    popover_height = $(this).children("img").attr("data-height")
    $("div#popover img").attr "src", popover_src
    $("div#popover img").attr "alt", popover_caption
    $("div#popover img").attr "width", popover_width
    $("div#popover img").attr "height", popover_height
    $("div#popover").fadeIn "fast"

  $("div#popover button").click (event) ->
    event.preventDefault()
    $("div#popover").fadeOut "fast"

Add event parameter to popover close button click


$(".js-bucket-list").sortable({
  connectWith: ".js-bucket-list"
  update: (event, ui) ->
    if this == ui.item.parent()[0]
      window.target    = $(event.target)
      bucket_id = target.data("bucket-id")
      position  = ui.item.index()

      console.log bucket_id, position

      $.ajax
        type: "POST"
        url: ui.item.data("prioritized-issue-path")
        dataType: "json"
        data: { prioritized_issue: { bucket_id: bucket_id, row_order_position: position } }
}).disableSelection();

$ ->
  $(".js-bucket-list").sortable({
    connectWith: ".js-bucket-list"
    update: (event, ui) ->
      if this == ui.item.parent()[0]
        window.target    = $(event.target)
        bucket_id = target.data("bucket-id")
        position  = ui.item.index()

        $.ajax
          type: "POST"
          url: ui.item.data("prioritized-issue-path")
          dataType: "json"
          data: { prioritized_issue: { bucket_id: bucket_id, row_order_position: position } }
  }).disableSelection();

Remove console log and wrap in document start


CurrentUser = require '../../../../models/current_user.coffee'
{ Following, FollowButton } = require '../../../../components/follow_button/index.coffee'

module.exports = (artists) ->
  user = CurrentUser.orNull()
  following = new Following if user

  ids = artists.map (artist) ->
    $el = $(".artist-follow[data-id='#{artist.id}']")

    new FollowButton
      el: $el
      following: following
      modelName: 'artist'
      model: artist
      href: "#{artist.href}/follow"
      context_page: "Show page"

    artist.id

  following.syncFollows ids if user

Backbone = require 'backbone'
CurrentUser = require '../../../../models/current_user.coffee'
{ Following, FollowButton } = require '../../../../components/follow_button/index.coffee'

module.exports = (artists) ->
  user = CurrentUser.orNull()
  following = new Following if user

  ids = artists.map (artist) ->
    $el = $(".artist-follow[data-id='#{artist.id}']")

    new FollowButton
      el: $el
      following: following
      modelName: 'artist'
      model: new Backbone.Model artist
      href: "#{artist.href}/follow"
      context_page: "Show page"

    artist.id

  following.syncFollows ids if user

Fix follow on show page

Fix follow on show page


describe 'acceptance', ->
  Given -> @subject = require '../lib/kindly'

  context 'sync', ->
    When -> @files = @subject.get('test/fixtures')
    Then -> expect(@files).to.deep.equal
      files: [ 'test/fixtures/baz.quux' , 'test/fixtures/foo.bar']
      directories: []
      other: []

  context 'async', ->
    When (done) -> @subject.get 'test/fixtures', (err, @files) => done()
    Then -> expect(@files).to.deep.equal
      files: [ 'test/fixtures/baz.quux' , 'test/fixtures/foo.bar']
      directories: []
      other: []

describe 'acceptance', ->
  Given -> @subject = require '../lib/kindly'

  context 'sync', ->
    When -> @descriptors = @subject.get('test/fixtures')
    Then -> expect(@descriptors.files).to.contain 'test/fixtures/baz.quux'
    And -> expect(@descriptors.files).to.contain 'test/fixtures/foo.bar'

  context 'async', ->
    When (done) -> @subject.get 'test/fixtures', (err, @descriptors) => done()
    Then -> expect(@descriptors.files).to.contain 'test/fixtures/baz.quux'
    And -> expect(@descriptors.files).to.contain 'test/fixtures/foo.bar'

Order matters on arrays, unfortunately

Order matters on arrays, unfortunately


runEvaluation = ->
  $('#result').text("Loading...")
  $.ajax {
    "url": "/jsontest",
    "type": "POST",
    "data": JSON.stringify({
      "language": $('#eval-language').val(),
      "code": $('textarea').val()
    }),
    "dataType": "json",
    "contentType": "application/json; charset=utf-8",
    "success": (data) =>
      $('#result').text(JSON.stringify(data, null, 4))
    "error": (jqXHR, textStatus, errorThrown) =>
      $('#result').text("[" + textStatus + "] " + errorThrown)
  }

$ ->
  $('textarea').keydown (event) =>
    if (event.keyCode == 10 or event.keyCode == 13) and event.ctrlKey
      runEvaluation()

  $('#eval-submit').click (event) =>
    runEvaluation()

runEvaluation = ->
  $('#result').text("Loading...")
  $.ajax {
    "url": "/api/evaluate",
    "type": "POST",
    "data": JSON.stringify({
      "language": $('#eval-language').val(),
      "code": $('textarea').val()
    }),
    "dataType": "json",
    "contentType": "application/json; charset=utf-8",
    "success": (data) =>
      $('#result').text(JSON.stringify(data, null, 4))
    "error": (jqXHR, textStatus, errorThrown) =>
      $('#result').text("[" + textStatus + "] " + errorThrown)
  }

$ ->
  $('textarea').keydown (event) =>
    if (event.keyCode == 10 or event.keyCode == 13) and event.ctrlKey
      runEvaluation()

  $('#eval-submit').click (event) =>
    runEvaluation()

Make /try hit the new endpoint.

Make /try hit the new endpoint.


#= require almond

window.ttm ||= {}

ttm.ClassMixer = (klass)->
  klass.build = ->
    it = new klass
    it.initialize && it.initialize.apply(it, arguments)
    it
  klass.prototype.klass = klass
  klass

define "lib/class_mixer", ->
  return ttm.ClassMixer

#= require almond

window.ttm ||= {}
window.decorators ||= {}

ttm.ClassMixer = (klass)->
  klass.build = ->
    it = new klass
    it.initialize && it.initialize.apply(it, arguments)
    it
  klass.prototype.klass = klass
  klass

define "lib/class_mixer", ->
  return ttm.ClassMixer

WIP: Implement decorator pattern for student dashboard.


# A collection of common, simple text response
# commands that don’t require data from elsewhere
#
# Command(s):
#   hubot schedule    — Return the current stream schedule
#   hubot social      — Links to Twitter and Facebook
#   hubot commands    - Shows a current list of commands
#   hubot bot         - A quick bot about/introduction

module.exports = (robot) ->
    robot.respond /schedule$/i, (msg) ->
        msg.send "Our current schedule is Tuesday, Thursday, Saturday 10pm-1am GMT and Sunday 10pm-12am GMT."

    robot.respond /social$/i, (msg) ->
        msg.send "You can find me on Twitter at http://twitter.com/masonest_ and on Facebook at http://facebook.com/masonest"

    robot.respond /commands$/i, (msg) ->
        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
            msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet, !bot and !shoutout."
            return

        msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet and !bot."

    robot.respond /bot$/i, (msg) ->
        msg.send "I’m Awebot. I am Masonest’s custom bot and was built by him. I’m still young and learning new things all the time."

# A collection of common, simple text response
# commands that don’t require data from elsewhere
#
# Command(s):
#   hubot schedule    — Return the current stream schedule
#   hubot social      — Links to Twitter and Facebook
#   hubot commands    - Shows a current list of commands
#   hubot bot         - A quick bot about/introduction

module.exports = (robot) ->
    robot.respond /schedule$/i, (msg) ->
        msg.send "Our current schedule is Tuesday, Thursday, Saturday 10pm-1am GMT and Sunday 10pm-12am GMT."

    robot.respond /social$/i, (msg) ->
        msg.send "You can find me on Twitter at http://twitter.com/masonest_ and on Facebook at http://facebook.com/masonest"

    robot.respond /commands$/i, (msg) ->
        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
            msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet, !bot, !points and !shoutout."
            return

        msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet, !points and !bot."

    robot.respond /bot$/i, (msg) ->
        msg.send "I’m Awebot. I am Masonest’s custom bot and was built by him. I’m still young and learning new things all the time."

Add points command to command list.

Add points command to command list.


Flow.Browser = (_) ->
  _docs = signals []

  _sortedDocs = lift _docs, (docs) ->
    sortBy docs, (doc) -> -doc.date().getTime()

  _hasDocs = lift _docs, (docs) -> docs.length > 0

  createNotebookView = (notebook) ->
    _name = notebook.name
    _date = signal new Date notebook.timestamp_millis
    _fromNow = lift _date, Flow.Util.fromNow

    load = ->
      _.confirm 'Are you sure you want to load this notebook?', { acceptCaption: 'Load Notebook', declineCaption: 'Cancel' }, (accept) ->
        if accept
          _.requestObject 'notebook', _name, (error, doc) ->
            if error
              _.alert error.message
            else
              _.load _name, doc

    purge = ->
      _.requestDeleteObject 'notebook', _name, (error) ->
        if error
          debug error
        else
          _docs.remove self

    self =
      name: _name
      date: _date
      fromNow: _fromNow
      load: load
      purge: purge

  loadNotebooks = ->
    _.requestObjects 'notebook', (error, notebooks) ->
      if error
        debug error
      else
        #XXX sort
        _docs map notebooks, (notebook) -> createNotebookView notebook

  link _.ready, ->
    loadNotebooks()

    link _.saved, ->
      loadNotebooks()

  docs: _sortedDocs
  hasDocs: _hasDocs
  loadNotebooks: loadNotebooks

Flow.Browser = (_) ->
  _docs = signals []

  _sortedDocs = lift _docs, (docs) ->
    sortBy docs, (doc) -> -doc.date().getTime()

  _hasDocs = lift _docs, (docs) -> docs.length > 0

  createNotebookView = (notebook) ->
    _name = notebook.name
    _date = signal new Date notebook.timestamp_millis
    _fromNow = lift _date, Flow.Util.fromNow

    load = ->
      _.confirm 'This action will replace your active notebook.\nAre you sure you want to continue?', { acceptCaption: 'Load Notebook', declineCaption: 'Cancel' }, (accept) ->
        if accept
          _.requestObject 'notebook', _name, (error, doc) ->
            if error
              _.alert error.message
            else
              _.load _name, doc

    purge = ->
      _.requestDeleteObject 'notebook', _name, (error) ->
        if error
          debug error
        else
          _docs.remove self

    self =
      name: _name
      date: _date
      fromNow: _fromNow
      load: load
      purge: purge

  loadNotebooks = ->
    _.requestObjects 'notebook', (error, notebooks) ->
      if error
        debug error
      else
        #XXX sort
        _docs map notebooks, (notebook) -> createNotebookView notebook

  link _.ready, ->
    loadNotebooks()

    link _.saved, ->
      loadNotebooks()

  docs: _sortedDocs
  hasDocs: _hasDocs
  loadNotebooks: loadNotebooks

Improve warning message when loading notebooks


selenium = require 'selenium-webdriver'
{TestHelper} = require './test-element'

COMMON_ELEMENTS =
  courseLink: (appearance, isCoach = false) ->
    dataAttr = 'data-appearance'

    if appearance?
      dataAttr += "='#{appearance}'"

    if isCoach
      teacherLink = "[#{dataAttr}] > [href*='cc-dashboard']"
      studentLink = "[#{dataAttr}] > [href*='content']"
    else
      teacherLink = "[#{dataAttr}] > [href*='calendar']"
      studentLink = "[#{dataAttr}] > [href*='list']"

    css: "#{teacherLink}, #{studentLink}"

class CourseSelect extends TestHelper

  constructor: (test, testElementLocator) ->

    testElementLocator ?= '.course-listing'
    super(test, '.course-listing', COMMON_ELEMENTS)

  goTo: (category) ->
    @waitUntilLoaded()
    # Go to the bio dashboard
    switch category
      when 'BIOLOGY' then @el.courseLink.get('biology').click()
      when 'PHYSICS' then @el.courseLink.get('physics').click()
      when 'CONCEPT_COACH' then @el.courseLink.get(null, true).click()
      else @el.courseLink.get().click()

module.exports = CourseSelect

selenium = require 'selenium-webdriver'
{TestHelper} = require './test-element'

COMMON_ELEMENTS =
  courseLink: (appearance, isCoach = false) ->
    dataAttr = 'data-appearance'

    if appearance?
      dataAttr += "='#{appearance}'"

    if isCoach
      teacherLink = "[#{dataAttr}] > [href*='cc-dashboard']"
      studentLink = "[#{dataAttr}] > [href*='content']"
    else
      teacherLink = "[#{dataAttr}] > [href*='calendar']"
      studentLink = "[#{dataAttr}] > [href*='list']"

    css: "#{teacherLink}, #{studentLink}"

class CourseSelect extends TestHelper

  constructor: (test, testElementLocator) ->

    testElementLocator ?= '.course-listing'
    super(test, '.course-listing', COMMON_ELEMENTS)

  goTo: (category) ->
    @waitUntilLoaded()
    # Go to the bio dashboard
    switch category
      when 'BIOLOGY' then @el.courseLink.get('biology').click()
      when 'PHYSICS' then @el.courseLink.get('physics').click()
      when 'CONCEPT_COACH' then @el.courseLink.get(null, true).click()
      else @el.courseLink.get().click()

  goToCourseByName: (name) ->
    test.waitClick(css: "[data-title='#{name}'] > a")

module.exports = CourseSelect

Add helper method to goto a course by name


path = require 'path'
url = require 'url'
request = require 'request'
fs = require './fs'
TextMateTheme = require './text-mate-theme'

# Convert a TextMate theme to an Atom theme
module.exports =
class ThemeConverter
  constructor: (@sourcePath, destinationPath) ->
    @destinationPath = path.resolve(destinationPath)

  readTheme: (callback) ->
    {protocol} = url.parse(@sourcePath)
    if protocol is 'http:' or protocol is 'https:'
      request @sourcePath, (error, response, body) =>
        if error?
          if error.code is 'ENOTFOUND'
            error = "Could not resolve URL: #{@sourcePath}"
          callback(error)
        else  if response.statusCode isnt 200
          callback("Request to #{@sourcePath} failed (#{response.headers.status})")
        else
          callback(null, body)
    else
      sourcePath = path.resolve(@sourcePath)
      if fs.isFileSync(sourcePath)
        callback(null, fs.readFileSync(sourcePath, 'utf8'))
      else
        callback("TextMate theme file not found: #{sourcePath}")

  convert: (callback) ->
    @readTheme (error, themeContents) =>
      return callback(error) if error?

      try
        theme = new TextMateTheme(themeContents)
      catch error
        return callback(error)

      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'base.less'), theme.getStylesheet())
      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'syntax-variables.less'), theme.getSyntaxVariables())
      callback()

path = require 'path'
url = require 'url'
fs = require './fs'
request = require './request'
TextMateTheme = require './text-mate-theme'

# Convert a TextMate theme to an Atom theme
module.exports =
class ThemeConverter
  constructor: (@sourcePath, destinationPath) ->
    @destinationPath = path.resolve(destinationPath)

  readTheme: (callback) ->
    {protocol} = url.parse(@sourcePath)
    if protocol is 'http:' or protocol is 'https:'
      requestOptions = url: @sourcePath
      request.get requestOptions, (error, response, body) =>
        if error?
          if error.code is 'ENOTFOUND'
            error = "Could not resolve URL: #{@sourcePath}"
          callback(error)
        else  if response.statusCode isnt 200
          callback("Request to #{@sourcePath} failed (#{response.headers.status})")
        else
          callback(null, body)
    else
      sourcePath = path.resolve(@sourcePath)
      if fs.isFileSync(sourcePath)
        callback(null, fs.readFileSync(sourcePath, 'utf8'))
      else
        callback("TextMate theme file not found: #{sourcePath}")

  convert: (callback) ->
    @readTheme (error, themeContents) =>
      return callback(error) if error?

      try
        theme = new TextMateTheme(themeContents)
      catch error
        return callback(error)

      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'base.less'), theme.getStylesheet())
      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'syntax-variables.less'), theme.getSyntaxVariables())
      callback()

Use request helper in ThemeConverter class


app = require 'app'
fs = require 'fs'
path = require 'path'
protocol = require 'protocol'

# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation, and is used to create a
# custom resource loader by adding the 'atom' custom protocol.
module.exports =
class AtomProtocolHandler
  constructor: (@resourcePath) ->
    @loadPaths = [
      path.join(app.getHomeDir(), '.atom', 'dev', 'packages')
      path.join(app.getHomeDir(), '.atom', 'packages')
      path.join(@resourcePath, 'node_modules')
    ]

    @registerAtomProtocol()

  # Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))
      for loadPath in @loadPaths
        filePath = path.join(loadPath, relativePath)
        break if fs.statSyncNoException(filePath).isFile?()
      return new protocol.RequestFileJob(filePath)

app = require 'app'
fs = require 'fs'
path = require 'path'
protocol = require 'protocol'

# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation and is used to create a
# custom resource loader for 'atom://' URLs.
#
# The following directories are searched in order:
#   * ~/.atom/assets
#   * ~/.atom/dev/packages
#   * ~/.atom/packages
#   * RESOURCE_PATH/node_modules
#
module.exports =
class AtomProtocolHandler
  constructor: (@resourcePath) ->
    @loadPaths = [
      path.join(app.getHomeDir(), '.atom', 'dev', 'packages')
      path.join(app.getHomeDir(), '.atom', 'packages')
      path.join(@resourcePath, 'node_modules')
    ]

    @registerAtomProtocol()

  # Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))

      if relativePath.indexOf('assets/') is 0
        assetsPath = path.join(app.getHomeDir(), '.atom', relativePath)
        if fs.statSyncNoException(assetsPath).isFile?()
          filePath = assetsPath

      unless filePath
        for loadPath in @loadPaths
          filePath = path.join(loadPath, relativePath)
          break if fs.statSyncNoException(filePath).isFile?()

      new protocol.RequestFileJob(filePath)

Load atom://assets/ urls from ~/.atom/assets


Client = require('raven').Client

Client.prototype.captureDocumentError = (req, err) ->
  @captureMessage "#{req.type}: #{err.details[0].message}",
    level: 'warning'
    extra: req: req, err: err

module.exports = new Client process.env.SENTRY_DNS

module.exports.patchGlobal (id, err) ->
  console.error 'Uncaught Exception'
  console.error err.message
  console.error err.stack
  process.exit 1

Client = require('raven').Client

Client.prototype.captureDocumentError = (req, err) ->
  @captureMessage "#{req.type}: #{err.details[0].message}",
    level: 'warning'
    extra: req: req, err: err

module.exports = new Client process.env.SENTRY_DNS

if process.env.NODE_ENV isnt 'development'
  module.exports.patchGlobal (id, err) ->
    console.error 'Uncaught Exception'
    console.error err.message
    console.error err.stack
    process.exit 1

Disable logging to Sentry in development


app = require 'app'
fs = require 'fs'
path = require 'path'
protocol = require 'protocol'

# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation and is used to create a
# custom resource loader for 'atom://' URLs.
#
# The following directories are searched in order:
#   * ~/.atom/assets
#   * ~/.atom/dev/packages (unless in safe mode)
#   * ~/.atom/packages
#   * RESOURCE_PATH/node_modules
#
module.exports =
class AtomProtocolHandler
  constructor: (resourcePath, safeMode) ->
    @loadPaths = []
    @dotAtomDirectory = path.join(app.getHomeDir(), '.atom')

    unless safeMode
      @loadPaths.push(path.join(@dotAtomDirectory, 'dev', 'packages'))

    @loadPaths.push(path.join(@dotAtomDirectory, 'packages'))
    @loadPaths.push(path.join(resourcePath, 'node_modules'))

    @registerAtomProtocol()

  # Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))

      if relativePath.indexOf('assets/') is 0
        assetsPath = path.join(@dotAtomDirectory, relativePath)
        filePath = assetsPath if fs.statSyncNoException(assetsPath).isFile?()

      unless filePath
        for loadPath in @loadPaths
          filePath = path.join(loadPath, relativePath)
          break if fs.statSyncNoException(filePath).isFile?()

      new protocol.RequestFileJob(filePath)

app = require 'app'
fs = require 'fs'
path = require 'path'
protocol = require 'protocol'

# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation and is used to create a
# custom resource loader for 'atom://' URLs.
#
# The following directories are searched in order:
#   * ~/.atom/assets
#   * ~/.atom/dev/packages (unless in safe mode)
#   * ~/.atom/packages
#   * RESOURCE_PATH/node_modules
#
module.exports =
class AtomProtocolHandler
  constructor: (resourcePath, safeMode) ->
    @loadPaths = []

    unless safeMode
      @loadPaths.push(path.join(process.env.ATOM_HOME, 'dev', 'packages'))

    @loadPaths.push(path.join(process.env.ATOM_HOME, 'packages'))
    @loadPaths.push(path.join(resourcePath, 'node_modules'))

    @registerAtomProtocol()

  # Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))

      if relativePath.indexOf('assets/') is 0
        assetsPath = path.join(process.env.ATOM_HOME, relativePath)
        filePath = assetsPath if fs.statSyncNoException(assetsPath).isFile?()

      unless filePath
        for loadPath in @loadPaths
          filePath = path.join(loadPath, relativePath)
          break if fs.statSyncNoException(filePath).isFile?()

      new protocol.RequestFileJob(filePath)

Use ATOM_HOME env var in protocol handler

Closes #5501


H2O.PredictOutput = (_, prediction) ->
  { frame, model } = prediction

  _predictionTable = _.inspect 'prediction', prediction

  inspect = ->
    #XXX get this from prediction table
    _.insertAndExecuteCell 'cs', "inspect predict #{stringify model.key}, #{stringify frame.key.name}"

  predictionTable: _predictionTable
  inspect: inspect
  template: 'flow-predict-output'

H2O.PredictOutput = (_, prediction) ->
  { frame, model } = prediction

  _predictionTable = _.inspect 'prediction', prediction

  inspect = ->
    #XXX get this from prediction table
    _.insertAndExecuteCell 'cs', "inspect getPrediction #{stringify model.key}, #{stringify frame.key.name}"

  predictionTable: _predictionTable
  inspect: inspect
  template: 'flow-predict-output'

Use getPrediction() instead of predict() during inspection


"*":
  Zen:
    softWrap: false
    width: 160
  core:
    disabledPackages: [
      "notebook"
      "language-gfm"
      "wrap-guide"
    ]
    telemetryConsent: "limited"
    themes: [
      "atom-dark-ui"
      "atom-dark-syntax"
    ]
  "default-language":
    defaultLanguage: "Pandoc markdown"
  editor:
    fontSize: 16
    showIndentGuide: true
    softWrap: true
    tabLength: 4
  "exception-reporting":
    userId: "9d2cc651-880b-ad49-90cb-e8e61ea9a2b1"
  "markdown-preview":
    breakOnSingleNewline: true
  nvatom:
    directory: "~/Dropbox/notes"
  welcome:
    showOnStartup: false
".gfm.nvatom.source":
  editor:
    tabLength: 4
".gfm.source":
  editor:
    tabLength: 4

"*":
  Zen:
    softWrap: false
    width: 160
  core:
    disabledPackages: [
      "notebook"
      "language-gfm"
      "wrap-guide"
    ]
    telemetryConsent: "limited"
    themes: [
      "one-dark-ui"
      "solarized-dark-syntax"
    ]
  "default-language":
    defaultLanguage: "Pandoc markdown"
  editor:
    fontSize: 16
    showIndentGuide: true
    softWrap: true
    tabLength: 4
  "exception-reporting":
    userId: "9d2cc651-880b-ad49-90cb-e8e61ea9a2b1"
  "markdown-preview":
    breakOnSingleNewline: true
  nvatom:
    directory: "~/Dropbox/notes"
  welcome:
    showOnStartup: false
".gfm.nvatom.source":
  editor:
    tabLength: 4
".gfm.source":
  editor:
    tabLength: 4

Set Solarized Dark as editor theme and One Dark as UI theme


_ = require 'underscore'
React = require 'react'
EventEmitter2 = require 'eventemitter2'
Course = require '../course/model'
api = require '../api'

BLANK_USER =
  is_admin: false
  is_content_analyst: false
  is_customer_service: false
  name: null
  profile_url: null

User =

  channel: new EventEmitter2 wildcard: true

  update: (data) ->
    _.extend(this, data.user)
    @courses = _.map data.courses, (course) -> new Course(course)
    @channel.emit('change')

  logout: ->
    _.extend(this, BLANK_USER)
    @channel.emit('change')

  getCourse: (collectionUUID) ->
    _.findWhere @courses, ecosystem_book_uuid: collectionUUID

  ensureStatusLoaded: ->
    api.channel.emit("user.send.statusUpdate") unless @isLoggedIn()

  isLoggedIn: -> !!@profile_url

api.channel.on 'user.receive.*', ({data}) ->
  User.update(loaded: true)
  if data.access_token
    api.channel.emit('set.access_token', data.access_token)
  User.endpoints = data.endpoints
  if data.user then User.update(data.user) else User.logout()

# start out as a blank user
_.extend(User, BLANK_USER)

module.exports = User

_ = require 'underscore'
React = require 'react'
EventEmitter2 = require 'eventemitter2'
Course = require '../course/model'
api = require '../api'

BLANK_USER =
  is_admin: false
  is_content_analyst: false
  is_customer_service: false
  name: null
  profile_url: null
  courses: []

User =

  channel: new EventEmitter2 wildcard: true

  update: (data) ->
    _.extend(this, data.user)
    @courses = _.map data.courses, (course) => new Course(@, course)
    @channel.emit('change')

  logout: ->
    _.extend(this, BLANK_USER)
    @channel.emit('change')

  getCourse: (collectionUUID) ->
    _.findWhere( @courses, ecosystem_book_uuid: collectionUUID )

  findOrCreateCourse: (collectionUUID) ->
    @getCourse(collectionUUID) or (
      course = new Course(@, ecosystem_book_uuid: collectionUUID)
      @courses.push(course)
      course
    )

  ensureStatusLoaded: ->
    api.channel.emit("user.send.statusUpdate") unless @isLoggedIn()

  isLoggedIn: -> !!@profile_url

  onCourseUpdate: (course) ->
    @channel.emit('change')

api.channel.on 'user.receive.*', ({data}) ->
  User.update(loaded: true)
  if data.access_token
    api.channel.emit('set.access_token', data.access_token)
  User.endpoints = data.endpoints
  if data.user then User.update(data.user) else User.logout()

# start out as a blank user
_.extend(User, BLANK_USER)

module.exports = User

Add findOrCreate and a method for course updates


jQuery ->
  $('.has-postit').popover(
    placement: 'right', trigger: 'hover',
    template: '

'
  )

jQuery ->
  $('body').popover(
    selector: '.has-postit',
    placement: 'right', trigger: 'hover',
    template: '

'
  )

Make postits work for content loaded dynamically (see: http://stackoverflow.com/questions/9958825/how-do-i-bind-twitter-bootstrap-tooltips-to-dynamically-created-elements)


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

vm   = require './vm'
walk = require './walk'
{debounce, parseArgs} = require './utils'

module.exports = parseArgs (basePath, opts, cb) ->
  {relative, excluded} = opts
  opts.patch          ?= true
  opts.recurse        ?= true
  opts.watchSymlink   ?= false

  modules              = {}
  watching             = {}

  watch = (dir, isModule) ->
    return modules[dir] = true if isModule
    return if watching[dir]

    onchange = (event, filename) ->
      filename = path.join dir, filename

      return if excluded filename

      fs.stat filename, (err, stats) ->
        # ignore non-existent files
        return if err? or not stats?

        # watch new directory created
        if stats.isDirectory()
          watch filename

        # watch real dir of symbolic link
        else if stats.isSymbolicLink() and opts.watchSymlink
          fs.readLink filename, (err, realPath) ->
            watch path.dirname realPath

        # file changed
        else
          # Callback with file, stats object, module boolean
          cb filename, stats, modules[filename] ? false

    watching[dir] = fs.watch dir, (debounce 500, onchange)

  watch basePath

  if opts.recurse
    walk basePath, opts, (filename, stats) ->
      if excluded filename
        return

      watch filename if stats.isDirectory()

  if opts.patch
    vm (filename, stats) ->
      if excluded filename
        return

      watch filename, true if stats.isDirectory()

  watch

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

vm   = require './vm'
walk = require './walk'
{debounce, parseArgs} = require './utils'

module.exports = parseArgs (basePath, opts, cb) ->
  {relative, excluded} = opts
  opts.patch          ?= false
  opts.recurse        ?= true
  opts.watchSymlink   ?= false

  modules              = {}
  watching             = {}

  watch = (dir, isModule) ->
    return modules[dir] = true if isModule
    return if watching[dir]

    onchange = (event, filename) ->
      filename = path.join dir, filename

      return if excluded filename

      fs.stat filename, (err, stats) ->
        # ignore non-existent files
        return if err? or not stats?

        # watch new directory created
        if stats.isDirectory()
          watch filename

        # watch real dir of symbolic link
        else if stats.isSymbolicLink() and opts.watchSymlink
          fs.readLink filename, (err, realPath) ->
            watch path.dirname realPath

        # file changed
        else
          # Callback with file, stats object, module boolean
          cb filename, stats, modules[filename] ? false

    watching[dir] = fs.watch dir, (debounce 500, onchange)

  watch basePath

  if opts.recurse
    walk basePath, opts, (filename, stats) ->
      if excluded filename
        return

      watch filename if stats.isDirectory()

  if opts.patch
    vm (filename, stats) ->
      if excluded filename
        return

      watch filename, true if stats.isDirectory()

  watch

Disable vm patching by default.

Disable vm patching by default.


angular.module('doubtfire.projects.states.dashboard.directives.task-dashboard.directives.task-plagiarism-card', [])
#
# plagiarism of task information
#
.directive('taskPlagiarismCard', ->
  restrict: 'E'
  templateUrl: 'projects/states/dashboard/directives/task-dashboard/directives/task-plagiarism-card/task-plagiarism-card.tpl.html'
  scope:
    task: '='
  controller: ($scope, PlagiarismReportModal) ->
    $scope.viewReport = -> PlagiarismReportModal.show($scope.task)
)

angular.module('doubtfire.projects.states.dashboard.directives.task-dashboard.directives.task-plagiarism-card', [])
#
# plagiarism of task information
#
.directive('taskPlagiarismCard', ->
  restrict: 'E'
  templateUrl: 'projects/states/dashboard/directives/task-dashboard/directives/task-plagiarism-card/task-plagiarism-card.tpl.html'
  scope:
    task: '='
  controller: ($scope, PlagiarismReportModal) ->
    $scope.viewReport = -> PlagiarismReportModal.show
)

FIX: Use function pointer for plagiarism view report


class @Overlay
  constructor: (selector, options) ->
    @selector = selector
    @options = options
    self = this
    @overlay = $("

")

    $(@selector).click (event) ->
      event.preventDefault()
      self.open(this)

    $(document).on 'keyup', (event) ->
      return unless event.which == 27
      self.close()

  open: (target) ->
    overlayContent = $(target).find('.js-overlay').remove()

    @overlay.append(overlayContent)
    $('body').append(@overlay)

    maxWidth = $(window).width() - 20
    maxHeight = $(window).height() - 20

    commentWidth = 309
    maxPhotoWidth = 1024
    maxPhotoHeight = 768
    totalWidth = maxPhotoWidth + commentWidth

    if totalWidth > maxWidth
      width = maxWidth
    else
      width = totalWidth

    if maxPhotoHeight > maxHeight
      height = maxHeight
    else
      height = maxPhotoHeight

    heightRatio = height / maxPhotoHeight
    widthRatio = width / maxPhotoWidth

    if heightRatio > widthRatio
      height = maxPhotoHeight * widthRatio
    else if widthRatio > heightRatio
      width = (maxPhotoWidth * heightRatio) + commentWidth

    margin = (maxWidth - width) / 2

    top = window.scrollY + 10

    @overlay.css('left', margin)
    @overlay.css('top', top)
    @overlay.width(width)
    @overlay.height(height)

    $('body').addClass('scroll-lock')

    overlayContent.show()

  close: ->
    @overlay.hide()
    $('body').removeClass('scroll-lock')

class @Overlay
  constructor: (selector, options) ->
    @selector = selector
    @options = options
    self = this
    @overlay = $("

")
    $('body').append(@overlay)

    $(@selector).click (event) ->
      event.preventDefault()
      self.open(this)

    $(document).on 'keyup', (event) ->
      return unless event.which == 27
      self.close()

  open: (target) ->
    overlayContent = $(target).find('.js-overlay').clone()

    @overlay.append(overlayContent)

    maxWidth = $(window).width() - 20
    maxHeight = $(window).height() - 20

    commentWidth = 309
    maxPhotoWidth = 1024
    maxPhotoHeight = 768
    totalWidth = maxPhotoWidth + commentWidth

    if totalWidth > maxWidth
      width = maxWidth
    else
      width = totalWidth

    if maxPhotoHeight > maxHeight
      height = maxHeight
    else
      height = maxPhotoHeight

    heightRatio = height / maxPhotoHeight
    widthRatio = width / maxPhotoWidth

    if heightRatio > widthRatio
      height = maxPhotoHeight * widthRatio
    else if widthRatio > heightRatio
      width = (maxPhotoWidth * heightRatio) + commentWidth

    margin = (maxWidth - width) / 2

    top = window.scrollY + 10

    @overlay.css('left', margin)
    @overlay.css('top', top)
    @overlay.width(width)
    @overlay.height(height)
    @overlay.show()

    $('body').addClass('scroll-lock')

    overlayContent.show()

  close: ->
    @overlay.empty().hide()
    $('body').removeClass('scroll-lock')

Allow overlay to be opened multiple times


class NavigationMachineItem extends JView

  {Running, Stopped} = Machine.State

  stateClasses  = ''
  stateClasses += "#{state.toLowerCase()} " for state in Object.keys Machine.State

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

    machine            = data
    @alias             = machine.getName()
    path               = KD.utils.groupifyLink "/IDE/VM/#{machine.uid}"

    options.tagName    = 'a'
    options.cssClass   = "vm #{machine.status.state.toLowerCase()} #{machine.provider}"
    options.attributes =
      href             : path
      title            : "Open IDE for #{@alias}"

    super options, data

    @machine   = @getData()

    @label     = new KDCustomHTMLView
      partial  : @alias

    @progress  = new KDProgressBarView
      cssClass : 'hidden'

    { computeController } = KD.singletons

    computeController.on "public-#{@machine._id}", (event)=>

      {percentage, status} = event

      if percentage?

        if @progress.bar

          @progress.show()
          @progress.updateBar percentage

          if percentage is 100
            KD.utils.wait 1000, @progress.bound 'hide'

      else

        @progress.hide()

      if status?

        @unsetClass stateClasses
        @setClass status.toLowerCase()

  pistachio:->

    return """

      {{> @label}}

      {{> @progress}}
    """

class NavigationMachineItem extends JView

  {Running, Stopped} = Machine.State

  stateClasses  = ''
  stateClasses += "#{state.toLowerCase()} " for state in Object.keys Machine.State

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

    machine            = data
    @alias             = machine.getName()
    path               = KD.utils.groupifyLink "/IDE/VM/#{machine.uid}"

    options.tagName    = 'a'
    options.cssClass   = "vm #{machine.status.state.toLowerCase()} #{machine.provider}"
    options.attributes =
      href             : path
      title            : "Open IDE for #{@alias}"

    super options, data

    @machine   = @getData()

    @label     = new KDCustomHTMLView
      partial  : @alias

    @progress  = new KDProgressBarView
      cssClass : 'hidden'

    { computeController } = KD.singletons

    computeController.on "public-#{@machine._id}", (event)=>

      {percentage, status} = event

      if percentage?

        if @progress.bar

          @progress.show()
          @progress.updateBar percentage

          if percentage is 100
            KD.utils.wait 1000, @progress.bound 'hide'

      else

        @progress.hide()

      if status?

        @unsetClass stateClasses
        @setClass status.toLowerCase()

    @machine.on 'MachineDomainUpdated', @bound 'updateLabel'

  updateLabel: ->

    @label.updatePartial @machine.getName()

  pistachio:->

    return """

      {{> @label}}

      {{> @progress}}
    """

NavigationMachineItem: Update machine label on UI when machine domain updated.


window.ReactFeedSelection = React.createClass
  displayName: 'ReactFeedSelection'
  mixins: [UpdateOnSignInOrOutMixin]

  getInitialState: ->
    feedChoice: 'global'
    feeds:
      global: new GlobalFeedActivities
      personal: new PersonalFeedActivities

  _handleFeedChoiceChange: (e) ->
    if(e.target.checked)
      @setState
        feedChoice: e.target.value

  render: ->
    _div [],
      if currentSession.signedIn()
        _div ['feed-selection-row'],
          _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'global', id: 'FeedChoice_Global', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='global'  ]
          _label [ htmlFor: 'FeedChoice_Global' ],
            'Global'

          _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'personal', id: 'FeedChoice_Personal', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='personal' ]
          _label [ htmlFor: 'FeedChoice_Personal' ],
            'Personal'

          _div ['feed-selection-install-extension-button'],
            ReactInstallExtensionOrBookmarklet()

      ReactFeedActivitiesAutoLoading
        model: @state.feeds[@state.feedChoice]
        key: @state.feedChoice

window.ReactFeedSelection = React.createClass
  displayName: 'ReactFeedSelection'
  mixins: [UpdateOnSignInOrOutMixin]

  getInitialState: ->
    feedChoice: 'global'
    feeds:
      global: new GlobalFeedActivities
      personal: new PersonalFeedActivities

  _handleFeedChoiceChange: (e) ->
    if(e.target.checked)
      @setState
        feedChoice: e.target.value

  render: ->
    _div [],
      if currentSession.signedIn()
        _div ['feed-selection-row'],
          _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'global', id: 'FeedChoice_Global', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='global'  ]
          _label [ htmlFor: 'FeedChoice_Global' ],
            'Global'

          _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'personal', id: 'FeedChoice_Personal', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='personal' ]
          _label [ htmlFor: 'FeedChoice_Personal' ],
            'Personal'

          if !window.is_kennisland
            _div ['feed-selection-install-extension-button'],
              ReactInstallExtensionOrBookmarklet()

      ReactFeedActivitiesAutoLoading
        model: @state.feeds[@state.feedChoice]
        key: @state.feedChoice

Hide browser extension button on feed when kennisland


angular.module('loomioApp').factory 'GroupRecordsInterface', (BaseRecordsInterface, GroupModel) ->
  class GroupRecordsInterface extends BaseRecordsInterface
    model: GroupModel

    fetchByParent: (parentGroup) ->
      @fetch
        path: "#{parentGroup.id}/subgroups"
        cacheKey: "subgroupsFor#{parentGroup.key}"

    archive: (group) ->
      @restfulClient.postMember group.id, "archive"

angular.module('loomioApp').factory 'GroupRecordsInterface', (BaseRecordsInterface, GroupModel) ->
  class GroupRecordsInterface extends BaseRecordsInterface
    model: GroupModel

    fetchByParent: (parentGroup) ->
      @fetch
        path: "#{parentGroup.id}/subgroups"
        cacheKey: "subgroupsFor#{parentGroup.key}"

    archive: (group) =>
      @restfulClient.patchMember group.id, "archive"

Make archive group work again

Make archive group work again


# Description:
#   今日も一日頑張るぞい！
#
# Commands:
#   hubot zoi - 今日も一日頑張る
#
# Author:
#   Uchio KONDO

request = require 'request'
uri     = 'http://zoi.herokuapp.com/js/services.js'

getZois = (cb) ->
  request(uri, (err, response, body) ->
    if err
      cb.onError(err)
    else
      zois = body.match(/image: 'https:\/\/pbs.twimg.com\/media\/.+.jpg:large'/mg).
                  map((l) -> l.match("'(.+)'")[1])
      (cb.onZoi || cb.onSuccess)(zois)
  )

module.exports = (robot) ->
  robot.respond /zoi/i, (msg) ->
    getZois
      onZoi: (zois) -> msg.send msg.random zois
      onError: (err) -> msg.send "頑張れなかった...: #{err}"

# Description:
#   今日も一日頑張るぞい！
#
# Commands:
#   hubot zoi - 今日も一日頑張る
#
# Author:
#   Uchio KONDO

request = require 'request'
uri     = 'http://zoi.herokuapp.com/js/services.js'

getZois = (cb) ->
  request(uri, (err, response, body) ->
    if err
      cb.onError(err)
    else
      zois = body.match(/image: 'https:\/\/pbs.twimg.com\/media\/.+.jpg:large'/mg).
                  map((l) -> l.match("'(https.+\.jpg):large'")[1])
      (cb.onZoi || cb.onSuccess)(zois)
  )

module.exports = (robot) ->
  robot.respond /zoi/i, (msg) ->
    getZois
      onZoi: (zois) -> msg.send msg.random zois
      onError: (err) -> msg.send "頑張れなかった...: #{err}"

Remove :large suffix to usefully expand on limechat


# Source: https://github.com/lukehoban/atom-ide-flow/blob/master/lib/tooltip-view.coffee
{$, View} = require 'atom'

module.exports = (Main)->
  class TooltipView extends View
    @content: ->
      @div class: 'ide-hack-tooltip'

    initialize: (@rect, text = null) ->
      @text(text) if text?
      $(document.body).append this
      @updatePosition()

    # update tooltip text
    updateText: (text) ->
      @text(text)
      @updatePosition()

    # smart position update
    updatePosition: ->
      coords = [@rect.right, @rect.bottom, undefined]
      offset = 10

      # x axis adjust
      if coords[0] + this[0].offsetWidth >= $(document.body).width()
        coords[0] = $(document.body).width() - this[0].offsetWidth - offset
      if coords[0] < 0
        this.css({ 'white-space': 'pre-wrap' })
        coords[0] = offset
        coords[2] = offset

      # y axis adjust
      if coords[1] + this[0].offsetHeight >= $(document.body).height()
        coords[1] = @rect.top - this[0].offsetHeight

      this.css({ left: coords[0], top: coords[1], right: coords[2] })

# Source: https://github.com/lukehoban/atom-ide-flow/blob/master/lib/tooltip-view.coffee
{$, View} = require 'atom'

module.exports = (Main)->
  class TooltipView extends View
    @content: ->
      @div class: 'ide-hack-tooltip'

    initialize: (@rect, @LeMessage) ->
      @append LeMessage
      $(document.body).append this
      @updatePosition()

    # update tooltip text
    updateText: (text) ->
      @empty()
      @append @LeMessage
      @updatePosition()

    # smart position update
    updatePosition: ->
      coords = [@rect.right, @rect.bottom, undefined]
      offset = 10

      # x axis adjust
      if coords[0] + this[0].offsetWidth >= $(document.body).width()
        coords[0] = $(document.body).width() - this[0].offsetWidth - offset
      if coords[0] < 0
        this.css({ 'white-space': 'pre-wrap' })
        coords[0] = offset
        coords[2] = offset

      # y axis adjust
      if coords[1] + this[0].offsetHeight >= $(document.body).height()
        coords[1] = @rect.top - this[0].offsetHeight

      this.css({ left: coords[0], top: coords[1], right: coords[2] })

Append HTML instead of Text

:bug: Append HTML instead of Text


# Default callbacks for internal storage of received packets
class CableNotifications.Store.DefaultCallbacks
  constructor: (@collections) ->

  # Callbacks
  ##################################################

  collection_remove: (packet, collection) ->
    console.warn 'Method not implemented: collection_remove '

  create: (packet, collection) ->
    fields = packet.data
    collection.insert(fields)

  update: (packet, collection) ->
    collection.update({id: packet.id}, packet.data)

  update_many: (packet, collection) ->
    _.each packet.data, (fields) ->
      collection.update({id: fields.id}, fields)

  upsert_many: (packet, collection) ->
    _.each packet.data, (fields) ->
      collection.upsert({id: fields.id}, fields)

  destroy: (packet, collection) ->
    collection.remove({id: packet.id})

  error: (packet, collection) ->
    console.error "[#{packet.cmd}]: #{packet.error}"

# Default callbacks for internal storage of received packets
class CableNotifications.Store.DefaultCallbacks
  constructor: (@collections) ->

  # Callbacks
  ##################################################

  collection_remove: (packet, collection) ->
    console.warn 'Method not implemented: collection_remove '

  create: (packet, collection) ->
    fields = packet.data
    collection.create(fields)

  update: (packet, collection) ->
    collection.update({id: packet.id}, packet.data)

  update_many: (packet, collection) ->
    _.each packet.data, (fields) ->
      collection.update({id: fields.id}, fields)

  upsert_many: (packet, collection) ->
    _.each packet.data, (fields) ->
      collection.upsert({id: fields.id}, fields)

  destroy: (packet, collection) ->
    collection.destroy({id: packet.id})

  error: (packet, collection) ->
    console.error "[#{packet.cmd}]: #{packet.error}"

Fix default_callback wrong method names

Fix default_callback wrong method names


Range = require 'range'
Point = require 'point'

module.exports =
class Fold
  @idCounter: 1

  renderer: null
  startRow: null
  endRow: null

  constructor: (@renderer, @startRow, @endRow) ->
    @id = @constructor.idCounter++

  destroy: ->
    @renderer.destroyFold(this)

  getRange: ->
    throw "Don't worry about this yet -- sobo"

  getBufferDelta: ->
    new Point(@endRow - @startRow + 1, 0)

  handleBufferChange: (event) ->
    oldStartRow = @startRow

    { oldRange } = event
    if oldRange.start.row <= @startRow and oldRange.end.row >= @endRow
      @renderer.unregisterFold(oldStartRow, this)
      return

    changeInsideFold = @startRow <= oldRange.start.row and @endRow >= oldRange.end.row
    @startRow = @updateAnchorRow(@startRow, event)
    @endRow = @updateAnchorRow(@endRow, event)

    if @startRow != oldStartRow
      @renderer.unregisterFold(oldStartRow, this)
      @renderer.registerFold(@startRow, this)

    changeInsideFold

  updateAnchorRow: (row, event) ->
    { newRange, oldRange } = event
    return row if row < oldRange.start.row

    deltaFromOldRangeEndRow = row - oldRange.end.row
    newRange.end.row + deltaFromOldRangeEndRow

  compare: (other) ->
    other
    # startComparison = @start.compare(other.start)
    # if startComparison == 0
    #   other.end.compare(@end)
    # else
    #   startComparison

Range = require 'range'
Point = require 'point'

module.exports =
class Fold
  @idCounter: 1

  renderer: null
  startRow: null
  endRow: null

  constructor: (@renderer, @startRow, @endRow) ->
    @id = @constructor.idCounter++

  destroy: ->
    @renderer.destroyFold(this)

  getBufferDelta: ->
    new Point(@endRow - @startRow + 1, 0)

  handleBufferChange: (event) ->
    oldStartRow = @startRow

    { oldRange } = event
    if oldRange.start.row <= @startRow and oldRange.end.row >= @endRow
      @renderer.unregisterFold(oldStartRow, this)
      return

    changeInsideFold = @startRow <= oldRange.start.row and @endRow >= oldRange.end.row
    @startRow = @updateAnchorRow(@startRow, event)
    @endRow = @updateAnchorRow(@endRow, event)

    if @startRow != oldStartRow
      @renderer.unregisterFold(oldStartRow, this)
      @renderer.registerFold(@startRow, this)

    changeInsideFold

  updateAnchorRow: (row, event) ->
    { newRange, oldRange } = event
    return row if row < oldRange.start.row

    deltaFromOldRangeEndRow = row - oldRange.end.row
    newRange.end.row + deltaFromOldRangeEndRow

Remove unused methods from Fold

Remove unused methods from Fold


moment = require 'moment'
twix = require 'twix'
_ = require 'underscore'

durations =
  create: (startTime, endTime) ->
    moment(startTime).twix(endTime)

moment = require 'moment'
twix = require 'twix'
_ = require 'underscore'
{TimeStore} = require '../flux/time'

module.exports =
  create: (startTime, endTime) ->
    moment(startTime).twix(endTime)

  isPastDue: ({due_at}) ->
    moment(TimeStore.getNow()).isAfter(due_at)

Add isPastDue method and export methods

Add isPastDue method and export methods

I don't think this actually worked before? It didn't export anything


requirejs.config({
  paths: {
    fabric: [
      'lib/fabric']
  }
})

define ['game', 'synchronizedtime', 'position', 'lib/fabric'], (Game, SynchronizedTime, Position) -> # Fabric is deliberately not set as an argument
  console.log "Fabric: ", fabric
  console.log "Position: ", new Position([1,2], 0, 5)
  canvas = new fabric.Canvas('canvas', {renderOnAddRemove: false})
  window.canvas = canvas

  game = new Game(canvas)
  game.start()
  setInterval((->
    SynchronizedTime.time += 1
    game.timer_tick()
    ),
    1000
  )

requirejs.config({
  paths: {
    fabric: [
      'lib/fabric']
  }
})

define ['game', 'synchronizedtime', 'position', 'lib/fabric'], (Game, SynchronizedTime, Position) -> # Fabric is deliberately not set as an argument
  console.log "Fabric: ", fabric
  console.log "Position: ", new Position([1,2], 0, 5)
  canvas = new fabric.Canvas('canvas', {renderOnAddRemove: false})
  window.canvas = canvas

  game = new Game(canvas)
  game.browser = true
  game.start()
  setInterval((->
    SynchronizedTime.time += 1
    game.timer_tick()
    ),
    1000
  )

Set browser to true on Game class when in browser


'use strict'

mongoose = require 'mongoose'

module.exports = class Database
    constructor: ->
        @app = arguments[0]

        host = 'localhost'
        port = 27017
        database = 'app'

        @uri = 'mongodb://' + host + ':' + port + ':' + database

        return
    run: ->
        that = @

        @app.get('logger').info 'Helper loaded', {
            name: 'Database'
        }

        con = mongoose.connect @uri

        connect = con.connection

        connect.on 'error', (e) ->
            that.app.get('logger').error 'error connection to database', {
                error: e
            }
            return

        connect.on 'open', ->
            that.app.get('logger').info 'connection to database opend'

            that.app.get('event').emit 'connection to database ready', that.app
            return

        connect.on 'close', ->
            that.app.get('logger').warn 'connection to database close'

            that.app.get('event').emit 'connection to database close'
            return
        return

'use strict'

mongoose = require 'mongoose'

module.exports = class Database
    constructor: ->
        @app = arguments[0]

        host = 'localhost'
        port = 27017
        database = 'app'

        @uri = 'mongodb://' + host + ':' + port + '/' + database

        return
    run: ->
        that = @

        @app.get('logger').info 'Helper loaded', {
            name: 'Database'
        }

        con = mongoose.connect @uri

        connect = con.connection

        @app.set 'database', con

        connect.on 'error', (e) ->
            that.app.get('logger').error 'error connection to database', {
                error: e
            }
            return

        connect.on 'open', ->
            that.app.get('logger').info 'connection to database opend'

            that.app.get('event').emit 'connection to database ready', that.app
            return

        connect.on 'close', ->
            that.app.get('logger').warn 'connection to database close'

            that.app.get('event').emit 'connection to database close'
            return
        return

Fix bug when I want save data

Fix bug when I want save data


loadFragment = (locale, university, controller, param, modal) ->
  controller ?= ""
  param      ?= ""

  url = "/#{locale}/#{university}"
  if controller
    url += "/#{controller}"
    if param
      url += "/#{param}"

  url += ".fragment"

  contentWrap = $('#page-content')
  cached.getDOM url, (error, dom) ->
    initDom dom.clone().appendTo(contentWrap.empty())
  return true

testEnvironment = () -> $('body').attr('data-env') == 'test'

init_routing = () ->
  return if not History.enabled or testEnvironment()
  crossroads.addRoute "/{locale}/{university}/:controller:/:param:", loadFragment
  # crossroads.routed.add(console.log, console);

  $('a').live 'click', (ev) ->
    $this = $(this)
    href = $this.attr 'href'
    return false if $this.hasClass 'js-modal' or href == "#"
    if href.indexOf('http://') == 0 or $this.hasClass("js-reload")
      return true
    else
      navigate href
      return false

  History.Adapter.bind window, 'statechange', () ->
    navigate History.getState().hash

navigate = (url) ->
  History.pushState null, null, url
  crossroads.parse url

$(init_routing)

loadFragment = (locale, university, controller, param, modal) ->
  controller ?= ""
  param      ?= ""
  getParams  = null

  url = "/#{locale}/#{university}"
  if controller
    url += "/#{controller}"
    if param
      rest = param.split('?')
      getParams = rest[1]
      url += "/#{rest[0]}"
  url += ".fragment"
  if getParams
    url += "?#{getParams}"

  contentWrap = $('#page-content')
  cached.getDOM url, (error, dom) ->
    initDom dom.clone().appendTo(contentWrap.empty())
  return true

testEnvironment = () -> $('body').attr('data-env') == 'test'

init_routing = () ->
  return  if not History.enabled or testEnvironment()
  crossroads.addRoute "/{locale}/{university}/:controller:/:param:", loadFragment
  # crossroads.routed.add(console.log, console);

  $('a').live 'click', (ev) ->
    $this = $(this)
    href = $this.attr 'href'
    return false if $this.hasClass 'js-modal' or href == "#"
    if href.indexOf('http://') == 0 or $this.hasClass("js-reload")
      return true
    else
      navigate href
      return false

  History.Adapter.bind window, 'statechange', () ->
    navigate History.getState().hash

navigate = (url) ->
  History.pushState null, null, url
  crossroads.parse url

$(init_routing)

Support get parameters for fragment routing


{makeElement, defer} = Trix

prototypes =
  cursorTarget:
    makeElement
      tagName: "span"
      textContent: Trix.ZERO_WIDTH_SPACE
      data:
        trixSelection: true
        trixCursorTarget: true
        trixSerialize: false

Trix.extend
  selectionElements:
    selector: "[data-trix-selection]"

    cssText: """
      font-size: 0 !important;
      padding: 0 !important;
      margin: 0 !important;
      border: none !important;
    """

    create: (name) ->
      prototypes[name].cloneNode(true)

{makeElement, defer} = Trix

prototypes =
  cursorTarget:
    makeElement
      tagName: "span"
      textContent: Trix.ZERO_WIDTH_SPACE
      data:
        trixSelection: true
        trixCursorTarget: true
        trixSerialize: false

Trix.extend
  selectionElements:
    selector: "[data-trix-selection]"

    cssText: """
      font-size: 0 !important;
      padding: 0 !important;
      margin: 0 !important;
      border: none !important;
      line-height: 0 !important;
    """

    create: (name) ->
      prototypes[name].cloneNode(true)

Make sure selection elements have 0 line height to not offset line height.


define (require, exports, module) ->
  # This object stores generic information about the app
  # that might need to be available globally.
  config = {}

  # `set` is a shortcut for setting a bunch of properties by passing in an object.
  # Order isn't guaranteed. Call it multiple times if order is important.
  config.set = (options) ->
    for own key, value of options
      throw new Error 'Don\'t overwrite "set" in config.' if key is 'set'
      config[key] = value

  # Determine if we're running on a development server.
  config.set
    dev: +location.port > 1023 or !!~location.hostname.indexOf '.dev'
    demo: !!~location.href.indexOf 'demo'

  # Default host and API proxy path
  config.set
    apiHost: 'http://www.batdetective.org'
    proxyPath: '/_ouroboros_api/proxy'

  # TODO: What if dev Ouroboros isn't on 3000?
  config.set apiHost: "http://#{location.hostname}:3000" if config.dev

  config.set apiHost: "https://dev.zooniverse.org" if config.demo

  module.exports = config

define (require, exports, module) ->
  # This object stores generic information about the app
  # that might need to be available globally.
  config = {}

  # `set` is a shortcut for setting a bunch of properties by passing in an object.
  # Order isn't guaranteed. Call it multiple times if order is important.
  config.set = (options) ->
    for own key, value of options
      throw new Error 'Don\'t overwrite "set" in config.' if key is 'set'
      config[key] = value

  # Determine if we're running on a development server.
  config.set
    dev: +location.port > 1023 or !!~location.hostname.indexOf '.dev'
    demo: !!~location.href.indexOf 'demo'

  # Default host and API proxy path
  config.set
    apiHost: 'http://www.batdetective.org'
    proxyPath: '/_ouroboros_api/proxy'

  # TODO: What if dev Ouroboros isn't on 3000?
  if config.dev
    config.set
      apiHost: 'https://dev.zooniverse.org'
      proxyPath: '/proxy'

  config.set apiHost: "https://dev.zooniverse.org" if config.demo

  module.exports = config

Use dev.zooniverse.org for local development


# An animation sequence is an array of frames where each frame includes:
# * A pattern
# * A duration (ms)
class AnimationSequence

  # Initializes an animation sequence with a pattern and a duration for that pattern
  # @param pattern A single pattern or array of patterns
  # @param duration A single duration or array of durations
  constructor: (pattern, duration) ->
    @patterns = [].concat(pattern)
    @durations = [].concat(duration)

  # Returns a concatted animation sequence
  concat: (animationSequence) ->
    newPatterns = @patterns.concat(animationSequence.patterns)
    newDurations = @durations.concat(animationSequence.durations)
    return new AnimationSequence(newPatterns, newDurations)

  # Sets the speed for animation interpolation
  setSpeed: (@animationSpeed) ->

  # Interpolates between two frames
  # @param pattern The next pattern to animate to.
  # @param duration The duration that the interpolation should take
  interpolate: (pattern, duration) ->
    # TODO

Add extra line at end of file

Add extra line at end of file


JSV = require("JSV").JSV

class SchemaValidator

  constructor: (@schema_manager) ->
    @jsv = JSV.createEnvironment("json-schema-draft-03")
    for schema in @schema_manager.schemas
      @jsv.createSchema(schema, false, schema.id)

  validate: (identifier, data) ->
    schema = @schema_manager.find(identifier)
    if schema
      result = @jsv.findSchema(schema.id).validate data, schema
      if result.errors
        result.description = {}
        for error in result.errors
          data_property = @format_name(error.uri)
          result.description[data_property] = @format_error(error)
      result
    else
      throw "unknown schema id: #{id}"

  format_name: (data_uri) ->
    [root, data_property] = data_uri.split("#/")
    data_property || "<root>"
    data_property
      .replace /(\d+)\//g, (match, p1) -> "[#{p1}]/"
      .replace(/\//g, ".")

  format_error: (error) ->
    [root, property] = error.uri.split("#/")
    schema_property = error.schemaUri.split(":")[2]
    property ||= "<root>"
    out =
      message: error.message
      schema: schema_property

    switch error.message
      when "Instance is not a required type"
        out.types = error.details
      when "Number is greater than the required maximum value"
        out.maximum = error.details
      when "Number is less than the required minimum value"
        out.minimum = error.details

    out

module.exports = SchemaValidator

JSV = require("JSV").JSV

class SchemaValidator

  constructor: (@schema_manager) ->
    @jsv = JSV.createEnvironment("json-schema-draft-03")
    for schema in @schema_manager.schemas
      @jsv.createSchema(schema, false, schema.id)

  validate: (identifier, data) ->
    schema = @schema_manager.find(identifier)
    if schema
      result = @jsv.findSchema(schema.id).validate data, schema
      if result.errors
        result.description = {}
        for error in result.errors
          data_property = @format_name(error.uri)
          result.description[data_property] = @format_error(error)
      result
    else
      throw "unknown schema id: #{id}"

  format_name: (data_uri) ->
    [root, data_property] = data_uri.split("#/")
    data_property ?= "<root>"
    data_property
      .replace /(\d+)\//g, (match, p1) -> "[#{p1}]/"
      .replace(/\//g, ".")

  format_error: (error) ->
    [root, property] = error.uri.split("#/")
    schema_property = error.schemaUri.split(":")[2]
    property ||= "<root>"
    out =
      message: error.message
      schema: schema_property

    switch error.message
      when "Instance is not a required type"
        out.types = error.details
      when "Number is greater than the required maximum value"
        out.maximum = error.details
      when "Number is less than the required minimum value"
        out.minimum = error.details

    out

module.exports = SchemaValidator

Set default data property in schema validation when formatting


completionDelay = 100

exports.triggerAutocompletion = (editor, moveCursor = true, char = 'f') ->
  if moveCursor
    editor.moveToBottom()
    editor.moveToBeginningOfLine()
  editor.insertText(char)
  exports.waitForAutocomplete()

exports.waitForAutocomplete = ->
  advanceClock(completionDelay)
  waitsFor 'autocomplete to show', (done) ->
    setImmediate ->
      advanceClock(10)
      setImmediate ->
        advanceClock(10)
        done()

exports.buildIMECompositionEvent = (event, {data, target} = {}) ->
  event = new CustomEvent(event, {bubbles: true})
  event.data = data
  Object.defineProperty(event, 'target', {get: -> target})
  event

exports.buildTextInputEvent = ({data, target}) ->
  event = new CustomEvent('textInput', {bubbles: true})
  event.data = data
  Object.defineProperty(event, 'target', {get: -> target})
  event

completionDelay = 100

beforeEach ->
  spyOn(atom.views, 'readDocument').andCallFake (fn) -> fn()
  spyOn(atom.views, 'updateDocument').andCallFake (fn) -> fn()

exports.triggerAutocompletion = (editor, moveCursor = true, char = 'f') ->
  if moveCursor
    editor.moveToBottom()
    editor.moveToBeginningOfLine()
  editor.insertText(char)
  exports.waitForAutocomplete()

exports.waitForAutocomplete = ->
  advanceClock(completionDelay)
  waitsFor 'autocomplete to show', (done) ->
    setImmediate ->
      advanceClock(10)
      setImmediate ->
        advanceClock(10)
        done()

exports.buildIMECompositionEvent = (event, {data, target} = {}) ->
  event = new CustomEvent(event, {bubbles: true})
  event.data = data
  Object.defineProperty(event, 'target', {get: -> target})
  event

exports.buildTextInputEvent = ({data, target}) ->
  event = new CustomEvent('textInput', {bubbles: true})
  event.data = data
  Object.defineProperty(event, 'target', {get: -> target})
  event

Make updateDocument and readDocument sync


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

# For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps

'.table-edit':
  'tab': 'core:move-right'
  'shift-tab': 'core:move-left'

'.table-edit atom-text-editor':
  'enter': 'core:confirm'
  'ctrl-enter': 'editor:newline'
  'tab': 'table-edit:move-right'
  'shift-tab': 'table-edit:move-left'
  'ctrl-tab': 'editor:indent'

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

# For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps

'.table-edit':
  'tab': 'core:move-right'
  'shift-tab': 'core:move-left'
  'cmd-shift-right': 'table-edit:select-to-end-of-line'
  'cmd-shift-left': 'table-edit:select-to-beginning-of-line'
  'cmd-shift-up': 'table-edit:select-to-beginning-of-table'
  'cmd-shift-down': 'table-edit:select-to-end-of-table'

'.table-edit atom-text-editor':
  'enter': 'core:confirm'
  'ctrl-enter': 'editor:newline'
  'tab': 'table-edit:move-right'
  'shift-tab': 'table-edit:move-left'
  'ctrl-tab': 'editor:indent'

Add keymap for new selection expansion

Add keymap for new selection expansion


define [
  "chaplin"
  "lib/utils"
], (Chaplin, utils) ->
  "use strict"

  class Model extends Chaplin.Model

    urlRoot: utils.bnwUrl "/api"

    apiCall: (url = @url(), data = undefined) ->
      reqData = if data? then data else @query
      $.ajax
        url: url
        type: if data? then "POST" else "GET"
        data: reqData
        # This should actually be auto-detectable (backend do return
        # correct Content-type header) but jQuery sucks.
        dataType: "json"

define [
  "jquery"
  "chaplin"
  "lib/utils"
], ($, Chaplin, utils) ->
  "use strict"

  class Model extends Chaplin.Model

    urlRoot: utils.bnwUrl "/api"

    apiCall: (url = @url(), data = undefined) ->
      deferred = $.Deferred()
      promise = deferred.promise()
      reqData = if data? then data else @query
      jqxhr = $.ajax
        url: url
        type: if data? then "POST" else "GET"
        data: reqData
        # This should actually be auto-detectable (backend do return
        # correct Content-type header) but jQuery sucks.
        dataType: "json"
      jqxhr.done (data) =>
        # XXX: Not sure if GC will correctly clean up callbacks on 'done'
        deferred.resolve data unless @disposed
      promise

Fix errors appearing after long requests


express = require 'express'
stylus = require 'stylus'
assets = require 'connect-assets'

app = express.createServer()
app.use assets()
app.set 'view engine', 'jade'

app.get '/', (req, resp) -> resp.render 'index'

app.get '/:filename', (req, resp) ->
  content = req.query['content']
  if (content and content.length > 1024)
    resp.json({error: 'File content is longer than 1024 characters and was truncated.. Please use POST instead of GET'}, 406)
  else
    saveit(req, resp)

app.post '/:filename', saveit

saveit = (req, resp) ->
  content = req.query["content"]
  unless (content)
    resp.json({error: 'File content is missing'}, 406)
  else
    resp.attachment(req.params.filename)
    resp.send(content)

app.listen process.env.PORT or 4000, -> console.log 'Listening...'

express = require 'express'
stylus = require 'stylus'
assets = require 'connect-assets'

app = express.createServer()
app.use assets()
app.set 'view engine', 'jade'

app.get '/', (req, resp) -> resp.render 'index'

app.get '/:filename.:extension', (req, resp) ->
  content = req.param['content']
  if (content and content.length > 1024)
    resp.json({error: 'File content is longer than 1024 characters and was truncated.. Please use POST instead of GET'}, 406)
  else
    saveit(req, resp)

app.post '/:filename.:extension', saveit

saveit = (req, resp) ->
  content = req.param.content
  unless (content)
    resp.json({error: 'File content is missing'}, 406)
  else
    resp.attachment(req.param.filename)
    resp.send(content)

app.listen process.env.PORT or 4000, -> console.log 'Listening...'

Improve how params and routes are looked up


angular.module('gi.security').controller 'usersController'
, ['$scope', '$location', 'User', 'Auth'
, ($scope, $location, User, Auth) ->

  $scope.newUser = User.create()
  $scope.currentView = 'list'

  $scope.getData = () ->
    User.query (results) ->
      $scope.users = results
      $scope.selectedUser = $scope.users[0]

  $scope.deleteUser = (id) ->
    User.delete {id: id}
    , () ->
      $scope.getData()

  $scope.saveUser = (user) ->
    User.save user, () ->
      $scope.getData()

  $scope.getUsers = () ->
    $scope.users = User.query()

  $scope.deleteUser = (id) ->
    User.delete {id: id}
    , () ->
      $scope.getUsers()

  $scope.selectUser = (user) ->
    $scope.selectedUser = user

  $scope.show = (view) ->
    $scope.currentView = view

  Auth.isAdmin().then (isAdmin) ->
    if isAdmin
      $scope.getData()
    else
      $location.path '/login'

]

angular.module('gi.security').controller 'usersController'
, ['$scope', '$location', 'User', 'Auth'
, ($scope, $location, User, Auth) ->

  $scope.newUser = {}
  $scope.currentView = 'list'

  $scope.getData = () ->
    User.query (results) ->
      $scope.users = results
      $scope.selectedUser = $scope.users[0]

  $scope.deleteUser = (id) ->
    User.delete {id: id}
    , () ->
      $scope.getData()

  $scope.saveUser = (user) ->
    User.save user, () ->
      $scope.getData()

  $scope.getUsers = () ->
    $scope.users = User.query()

  $scope.deleteUser = (id) ->
    User.delete {id: id}
    , () ->
      $scope.getUsers()

  $scope.selectUser = (user) ->
    $scope.selectedUser = user

  $scope.show = (view) ->
    $scope.currentView = view

  Auth.isAdmin().then (isAdmin) ->
    if isAdmin
      $scope.getData()
    else
      $location.path '/login'

]

Remove deprecated User.create{} method call


crypto = require 'crypto'
_ = require 'underscore'

Singleton = require 'singleton'
Riak = require "#{__dirname}/../db/riak"
TimeEntry = require "#{__dirname}/../models/time_entry"

class TimeEntriesController extends Singleton
  bucket: 'time_entries'
  idGen: ()->
    sha = crypto.createHash 'sha1'
    _.each arguments, (d)->
      sha.update JSON.stringify d
    sha.digest 'hex'

  create: (time_entry, callback)->
    if time_entry.userId and time_entry.projectId
      id = @idGen time_entry

      handler = (err, obj, meta) ->
        if err
          callback err
        else
          te = new TimeEntry obj
          te.id = id
          callback null, te

      console.log id, @bucket
      Riak.getClient().save @bucket, id, time_entry,
        {
          index:
            userId: time_entry.userId
            projectId: time_entry.projectId
        }, handler

module.exports = TimeEntriesController.get()

crypto = require 'crypto'
_ = require 'underscore'

Singleton = require 'singleton'
Riak = require "#{__dirname}/../db/riak"
TimeEntry = require "#{__dirname}/../models/time_entry"

class TimeEntriesController extends Singleton
  bucket: 'time_entries'
  idGen: ()->
    sha = crypto.createHash 'sha1'
    _.each arguments, (d)->
      sha.update JSON.stringify d
    sha.digest 'hex'

  create: (time_entry, callback)->
    if time_entry.userId and time_entry.projectId
      time_entry.id = @idGen time_entry

      handler = (err, obj, meta) ->
        console.log err, obj, meta
        if err
          callback err
        else
          callback null, time_entry

      Riak.getClient().save @bucket, time_entry.id, time_entry,
        {
          index:
            userId: time_entry.userId
            projectId: time_entry.projectId
        }, handler

module.exports = TimeEntriesController.get()

Send back the original time entry

Send back the original time entry


require ['jquery', 'lib/core/base', 'flamsteed', 'lib/utils/scroll_perf', 'polyfills/function_bind', 'trackjs'], ($, Base, _FS, ScrollPerf) ->
  $ ->
    base = new Base()
    new ScrollPerf
    window.lp.fs = new _FS({events: window.lp.fs.buffer, u: $.cookies.get('lpUid')})
    require ['sailthru'], ->
      Sailthru.setup(domain: 'horizon.lonelyplanet.com')

require ['jquery', 'lib/core/base', 'flamsteed', 'lib/utils/scroll_perf', 'polyfills/function_bind', 'trackjs'], ($, Base, _FS, ScrollPerf) ->
  $ ->
    base = new Base()
    new ScrollPerf

    # Currently we can't serve Flamsteed over https because of f.staticlp.com
    # New community is using this file rather than app_secure_core
    # Trello card: https://trello.com/c/2RCd59vk/201-move-f-staticlp-com-off-cloudfront-and-on-to-fastly-so-we-can-serve-over-https
    unless window.location.protocol is "https:"
      window.lp.fs = new _FS({events: window.lp.fs.buffer, u: $.cookies.get('lpUid')})

    require ['sailthru'], ->
      Sailthru.setup(domain: 'horizon.lonelyplanet.com')

Remove flamsteed from secure pages

Remove flamsteed from secure pages


$ ->
    $body = $('body')
    bodyClass = $body.attr 'class'
    routes = ['startups-new', 'startups-edit']

    if bodyClass in ['startups-new', 'startups-edit']
        $markets = $("#startup_market_list")

        $('.upload-photo').click (event) ->
          event.preventDefault()
          $('#startup_image').click()

        $('#startup_image').change (event) ->
          if @files && @files[0]
              reader = new FileReader()

              reader.onload = (e) ->
                $('img.startuplogo').attr('src', e.target.result)

              reader.readAsDataURL(@files[0])

        $('.reset-photo').click (event) ->
          event.preventDefault()
          # TODO: Set a flag to delete image
          $('form').submit()

        $markets.tokenInput "http://api.angel.co/1/search?type=MarketTag",
          crossDomain: true,
          queryParam: "query",
          prePopulate: $markets.data('pre'),
          theme: "facebook",
          tokenLimit: 3,
          tokenValue: "name"
    else if bodyClass is 'startups-index'
        $(".startupcard").equalHeights()

$ ->
    $body = $('body')
    bodyClass = $body.attr 'class'
    routes = ['startups-new', 'startups-edit']

    if bodyClass in ['startups-new', 'startups-edit']
        $markets = $("#startup_market_list")

        $('.upload-photo').click (event) ->
          event.preventDefault()
          $('#startup_image').click()

        $('#startup_image').change (event) ->
          if @files && @files[0]
              reader = new FileReader()

              reader.onload = (e) ->
                $('img.startuplogo').attr('src', e.target.result)

              reader.readAsDataURL(@files[0])

        $('.reset-photo').click (event) ->
          event.preventDefault()
          $('input#startup_default_logo').val('set_default')
          $('img.startuplogo').attr('src', '/assets/fallback/thumb_startup-default.png')

        $markets.tokenInput "http://api.angel.co/1/search?type=MarketTag",
          crossDomain: true,
          queryParam: "query",
          prePopulate: $markets.data('pre'),
          theme: "facebook",
          tokenLimit: 3,
          tokenValue: "name"
    else if bodyClass is 'startups-index'
        $(".startupcard").equalHeights()

Add reset company image behavior for Startups.coffee


define [
    '../../core'
    'tpl!templates/views/columns.html'
    'plugins/jquery-ui'
], (c, templates...) ->

define [
    '../../core'
    'tpl!templates/views/columns-avail.html'
    'tpl!templates/views/columns-select.html'
    'tpl!templates/views/columns-selected.html'
    'plugins/jquery-ui'
], (c, templates...) ->

Include templates in column.coffee deps.


root = exports ? this

class DateChooser

  constructor: (chooser) ->
    @chooser = chooser
    selectors = chooser.find(".date-chooser-select") # Today/Tomorrow selection
    initialDate = selectors.data("initial-date")
    @dateDisplay = chooser.find(".date-chooser-display")
    @values = chooser.find(".date-chooser-values")
    @today = selectors.find(".today")
    @tomorrow = selectors.find(".tomorrow")

    switch initialDate
      when "today"
        @enableLink @tomorrow, @today
      when "tomorrow"
        @enableLink @today, @tomorrow
      else
        @enableLink @today, @tomorrow
        @enableLink @tomorrow, @today

  enableLink: (selector, otherSelector) =>
    selector.html( "<a href>" + selector.text() + "</a>" )
    selector.on "click", (event) =>
      @toggleDate( selector, otherSelector )
      event.preventDefault()

  toggleDate: (selector, otherSelector) =>
    selector.html( selector.text() )
    @setDate selector
    @enableLink otherSelector, selector

  setDate: (selector) =>
    day = selector.data("day")
    month = selector.data("month")
    year = selector.data("year")
    display = selector.data("date-display")
    @values.find(".day").eq(0).val(day)
    @values.find(".month").eq(0).val(month)
    @values.find(".year").eq(0).val(year)
    @dateDisplay.text(display)

root.DateChooser = DateChooser

root = exports ? this

class DateChooser

  constructor: (chooser) ->
    @chooser = chooser
    selectors = chooser.find(".date-chooser-select") # Today/Tomorrow selection
    initialDate = selectors.data("initial-date")
    @dateDisplay = chooser.find(".date-chooser-display")
    @values = chooser.find(".date-chooser-values")
    @today = selectors.find(".today")
    @tomorrow = selectors.find(".tomorrow")

    @today.on "click", (event) =>
      @toggleDate( @today, @tomorrow )
      event.preventDefault()

    @tomorrow.on "click", (event) =>
      @toggleDate( @tomorrow, @today )
      event.preventDefault()

    switch initialDate
      when "today"
        @enableLink @tomorrow
      when "tomorrow"
        @enableLink @today
      else
        @enableLink @today
        @enableLink @tomorrow

  enableLink: (selector) =>
    selector.html( "<a href>" + selector.text() + "</a>" )

  toggleDate: (selector, otherSelector) =>
    selector.html( selector.text() )
    @setDate selector
    @enableLink otherSelector

  setDate: (selector) =>
    day = selector.data("day")
    month = selector.data("month")
    year = selector.data("year")
    display = selector.data("date-display")
    @values.find(".day").eq(0).val(day)
    @values.find(".month").eq(0).val(month)
    @values.find(".year").eq(0).val(year)
    @dateDisplay.text(display)

root.DateChooser = DateChooser

Add on click event only once to each toggle element.


describe 'Linter Behavior', ->
  linter = null
  linterState = null
  bottomContainer = null
  trigger = (el, name) ->
    event = document.createEvent('HTMLEvents');
    event.initEvent(name, true, false);
    el.dispatchEvent(event);

  beforeEach ->
    waitsForPromise ->
      atom.packages.activatePackage('linter').then ->
        linter = atom.packages.getActivePackage('linter').mainModule.instance
        linterState = linter.state
        bottomContainer = linter.views.bottomContainer

  describe 'Bottom Tabs', ->
    it 'defaults to file tab', ->
      expect(linterState.scope).toBe('File')

    it 'changes tab on click', ->
      trigger(bottomContainer.getTab('Project'), 'click')
      expect(linterState.scope).toBe('Project')

    it 'toggles panel visibility on click', ->
      expect(linter.views.panel.panelVisibility).toBe(true)
      trigger(bottomContainer.getTab('File'), 'click')
      expect(linter.views.panel.panelVisibility).toBe(false)
      trigger(bottomContainer.getTab('File'), 'click')
      expect(linter.views.panel.panelVisibility).toBe(true)

:new: Add spec for visibility being toggled on tab double click


{CompositeDisposable} = require 'event-kit'

class MinimapHighlightSelected
  constructor: ->
    @subscriptions = new CompositeDisposable

  activate: (state) ->
    @highlightSelectedPackage = atom.packages.getLoadedPackage('highlight-selected')
    @minimapPackage = atom.packages.getLoadedPackage('minimap')

    return @deactivate() unless @highlightSelectedPackage? and @minimapPackage?

    @MinimapHighlightSelectedView = require('./minimap-highlight-selected-view')()

    @minimap = require @minimapPackage.path
    @highlightSelected = require @highlightSelectedPackage.path

    @minimap.registerPlugin 'highlight-selected', this

  deactivate: ->
    @deactivatePlugin()
    @minimapPackage = null
    @highlightSelectedPackage = null
    @highlightSelected = null
    @minimap = null

  isActive: -> @active
  activatePlugin: ->
    return if @active
    return unless @minimap.active

    @active = true

    @createViews()

    @subscriptions.add @minimap.onDidActivate @createViews
    @subscriptions.add @minimap.onDidDeactivate @destroyViews

  deactivatePlugin: ->
    return unless @active

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

  createViews: =>
    return if @viewsCreated

    @viewsCreated = true
    @view = new @MinimapHighlightSelectedView(@minimap)
    @view.handleSelection()

  destroyViews: =>
    return unless @viewsCreated
    @viewsCreated = false
    @view.removeMarkers()
    @view.destroy()

module.exports = new MinimapHighlightSelected

{CompositeDisposable} = require 'event-kit'

class MinimapHighlightSelected
  constructor: ->
    @subscriptions = new CompositeDisposable

  activate: (state) ->
    @highlightSelectedPackage = atom.packages.getLoadedPackage('highlight-selected')
    @minimapPackage = atom.packages.getLoadedPackage('minimap')

    return @deactivate() unless @highlightSelectedPackage? and @minimapPackage?

    @MinimapHighlightSelectedView = require('./minimap-highlight-selected-view')()

    @minimap = require @minimapPackage.path
    @highlightSelected = require @highlightSelectedPackage.path

    return @deactivate() unless @minimap.versionMatch('3.x')

    @minimap.registerPlugin 'highlight-selected', this

  deactivate: ->
    @deactivatePlugin()
    @minimapPackage = null
    @highlightSelectedPackage = null
    @highlightSelected = null
    @minimap = null

  isActive: -> @active
  activatePlugin: ->
    return if @active
    return unless @minimap.active

    @active = true

    @createViews()

    @subscriptions.add @minimap.onDidActivate @createViews
    @subscriptions.add @minimap.onDidDeactivate @destroyViews

  deactivatePlugin: ->
    return unless @active

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

  createViews: =>
    return if @viewsCreated

    @viewsCreated = true
    @view = new @MinimapHighlightSelectedView(@minimap)
    @view.handleSelection()

  destroyViews: =>
    return unless @viewsCreated
    @viewsCreated = false
    @view.removeMarkers()
    @view.destroy()

module.exports = new MinimapHighlightSelected

Add version test for minimap v3

Add version test for minimap v3


require 'atom'
require 'window'
{createPeer, connectDocument} = require './session-utils'
{createSite, Document} = require 'telepath'

window.setDimensions(x: 0, y: 0, width: 800, height: 800)
atom.show()

peer = createPeer()
{sessionId} = atom.getLoadSettings()
connection = peer.connect(sessionId, reliable: true)
connection.on 'open', ->
  console.log 'connection opened'
  connection.once 'data', (data) ->
    console.log 'received document'
    atom.windowState = Document.deserialize(createSite(peer.id), data)
    connectDocument(atom.windowState, connection)
    window.setUpEnvironment('editor')
    window.startEditorWindow()

require 'atom'
require 'window'
$ = require 'jquery'
{$$} = require 'space-pen'
{createPeer, connectDocument} = require './session-utils'
{createSite, Document} = require 'telepath'

window.setDimensions(width: 350, height: 100)
window.setUpEnvironment('editor')
{sessionId} = atom.getLoadSettings()

loadingView = $$ ->
  @div style: 'margin: 10px; text-align: center', =>
    @div "Joining session #{sessionId}"
$(window.rootViewParentSelector).append(loadingView)
atom.show()

peer = createPeer()
connection = peer.connect(sessionId, reliable: true)
connection.on 'open', ->
  console.log 'connection opened'
  connection.once 'data', (data) ->
    loadingView.remove()
    console.log 'received document'
    atom.windowState = Document.deserialize(createSite(peer.id), data)
    connectDocument(atom.windowState, connection)
    window.startEditorWindow()

Add loading view when joining session

Add loading view when joining session


define (require) ->
  _ = require('underscore')
  settings = require('settings')

  shortcodes = settings.shortcodes
  inverseShortcodes = _.invert(shortcodes)

  return new class LinksHelper
    getPath: (page, data) ->
      url = settings.root

      switch page
        when 'contents'
          uuid = data.model.getVersionedId()
          uuid = inverseShortcodes[uuid] if inverseShortcodes[uuid]
          url += "contents/#{uuid}"
          url += ":#{data.page}" if data.page

      return url

    serializeQuery: (query) ->
      queryString = {}

      query.split('?').pop().split('&').forEach (prop) ->
        item = prop.split('=')
        queryString[decodeURIComponent(item.shift())] = decodeURIComponent(item.shift())

      return queryString

    param: (obj) ->
      str = []

      for p of obj
        if obj.hasOwnProperty(p)
          str.push("#{encodeURIComponent(p)}=#{encodeURIComponent(obj[p])}")

      return str.join("&")

Add methods to serialize and parameterize query strings


utils = window.Curve

module.exports =
class PointerTool
  constructor: (svg, {@selectionModel, @selectionView}={}) ->
    @_evrect = svg.node.createSVGRect();
    @_evrect.width = @_evrect.height = 1;

  activate: ->
    svg.on 'click', @onClick
    svg.on 'mousemove', @onMouseMove

  deactivate: ->
    svg.off 'click', @onClick
    svg.off 'mousemove', @onMouseMove

  onClick: (e) =>
    obj = @_hitWithIntersectionList(e)
    @selectionModel.setSelected(obj)
    return false if obj

  onMouseMove: (e) =>
    @selectionModel.setPreselected(@_hitWithIntersectionList(e))
    # @selectionModel.setPreselected(@_hitWithTarget(e))

  _hitWithTarget: (e) ->
    obj = null
    obj = utils.getObjectFromNode(e.target) if e.target != svg.node
    obj

  _hitWithIntersectionList: (e) ->
    @_evrect.x = e.clientX
    @_evrect.y = e.clientY
    nodes = svg.node.getIntersectionList(@_evrect, null)

    obj = null
    if nodes.length
      for i in [nodes.length-1..0]
        clas = nodes[i].getAttribute('class')
        continue if clas and clas.indexOf('invisible-to-hit-test') > -1
        obj = utils.getObjectFromNode(nodes[i])
        break

    obj

$ = require 'jquery'

utils = window.Curve

module.exports =
class PointerTool
  constructor: (svg, {@selectionModel, @selectionView}={}) ->
    @_evrect = svg.node.createSVGRect();
    @_evrect.width = @_evrect.height = 1;

  activate: ->
    svg.on 'click', @onClick
    svg.on 'mousemove', @onMouseMove

  deactivate: ->
    svg.off 'click', @onClick
    svg.off 'mousemove', @onMouseMove

  onClick: (e) =>
    obj = @_hitWithIntersectionList(e)
    @selectionModel.setSelected(obj)
    return false if obj

  onMouseMove: (e) =>
    @selectionModel.setPreselected(@_hitWithIntersectionList(e))
    # @selectionModel.setPreselected(@_hitWithTarget(e))

  _hitWithTarget: (e) ->
    obj = null
    obj = utils.getObjectFromNode(e.target) if e.target != svg.node
    obj

  _hitWithIntersectionList: (e) ->
    {left, top} = $(svg.node).offset()
    @_evrect.x = e.clientX - left
    @_evrect.y = e.clientY - top
    nodes = svg.node.getIntersectionList(@_evrect, null)

    obj = null
    if nodes.length
      for i in [nodes.length-1..0]
        clas = nodes[i].getAttribute('class')
        continue if clas and clas.indexOf('invisible-to-hit-test') > -1
        obj = utils.getObjectFromNode(nodes[i])
        break

    obj

Make event positions relative to node offset


module.exports = (grunt) ->

  # Project configuration.
  grunt.initConfig

    shell:
      'compile-client-side':
        options: stdout: true
        command: 'coffee -o ./ -c src/'
      'compile-server-side':
        options: stdout: true
        command: 'coffee -o public/scripts/compiled -c public/scripts/src/'
      'compile-css':
        options: stdout: true
        command: 'stylus public/styles/'

    concat:
      options:
        separator: ';'
      dist:
        src: ['public/scripts/compiled/*.js', 'public/scripts/compiled/views/*.js']
        dest: 'public/scripts/out.js'

    uglify:
      build:
        src: 'public/scripts/out.js'
        dest: 'public/scripts/out.min.js'

    clean:
      js: ['*.js', 'public/scripts/compiled/', 'public/scripts/*.js']
      css: 'public/styles/main.css'
      components: 'public/components'
      npm: 'node_modules'

  # Load the plugin that provides the 'uglify' task.
  grunt.loadNpmTasks 'grunt-contrib-uglify'
  grunt.loadNpmTasks 'grunt-contrib-concat'
  grunt.loadNpmTasks 'grunt-contrib-clean'
  grunt.loadNpmTasks 'grunt-shell'

  # Default task(s).
  grunt.registerTask 'default', ['compile', 'concat', 'uglify']
  grunt.registerTask 'compile', ['shell:compile-server-side', 'shell:compile-client-side']

module.exports = (grunt) ->

  # Project configuration.
  grunt.initConfig

    shell:
      'compile-client-side':
        options: stdout: true
        command: 'coffee -o ./ -c src/'
      'compile-server-side':
        options: stdout: true
        command: 'coffee -o public/scripts/compiled -c public/scripts/src/'
      'compile-css':
        options: stdout: true
        command: 'stylus public/styles/'

    concat:
      options:
        separator: ';'
      dist:
        src: ['public/scripts/compiled/*.js', 'public/scripts/compiled/views/*.js']
        dest: 'public/scripts/out.js'

    uglify:
      build:
        src: 'public/scripts/out.js'
        dest: 'public/scripts/out.min.js'

    clean:
      js: ['*.js', 'public/scripts/compiled/', 'public/scripts/*.js']
      css: 'public/styles/main.css'
      components: 'public/components'
      npm: 'node_modules'

  # Load the plugin that provides the 'uglify' task.
  grunt.loadNpmTasks 'grunt-contrib-uglify'
  grunt.loadNpmTasks 'grunt-contrib-concat'
  grunt.loadNpmTasks 'grunt-contrib-clean'
  grunt.loadNpmTasks 'grunt-shell'

  # Default task(s).
  grunt.registerTask 'default', ['compile', 'concat', 'uglify']
  grunt.registerTask 'compile', ['shell:compile-server-side', 'shell:compile-client-side', 'shell:compile-css']

Add compile-css to grunt compile task.

Add compile-css to grunt compile task.