fixed bug with multiple controllers

fixed bug with multiple controllers

--- src/live_controller.coffee
+++ src/live_controller.coffee
@@ -41,7 +41,6 @@
   params = state.params || {}
   Router[method](document.location.pathname, params)

-
 routes = {}
 routeMethods = ["get", "post", "put", "delete"]

@@ -51,7 +50,6 @@
   for i in [0...routes[method].length]
     obj = routes[method][i]
     matches = url.match obj.regex
-
     if matches

       new LiveView "/views" + url + ".html", {}, (view) ->
@@ -100,7 +98,10 @@
     router = {}
     filters = {before:[], after:[]}
     routeMethods.forEach (method) ->
-      routes[method] = []
+
+      if not _.isArray(routes[method])
+        routes[method] = []
+
       router[method] = (route, callback) ->
         if typeof route != "string"
           callback = route


Handle missing _reactives field

Handle missing _reactives field

--- publish.coffee
+++ publish.coffee
@@ -336,6 +336,9 @@
             added: (id, fields) =>
                 # log querySpec, "server says ADDED:", id, fields

+                fields = _.clone fields
+                fields._reactives ?= {}
+
                 if id of (fieldsByModelIdQuery?[querySpec.modelName] ? {})
                     # The set of projections being watched on this doc may have grown.
                     changedFields = {}
@@ -362,6 +365,9 @@

             changed: (id, fields) =>
                 # log querySpec, "server says CHANGED:", id, fields
+
+                fields = _.clone fields
+                fields._reactives ?= {}

                 changedFields = {}


Add javascript app router specs.

Add javascript app router specs.

--- spec/javascripts/dot_ledger/routers/app_spec.js.coffee
+++ spec/javascripts/dot_ledger/routers/app_spec.js.coffee
@@ -1,3 +1,129 @@
 describe "DotLedger.Routers.App", ->
+  navigateOptions = {trigger: true}
+  createRouter = (spy)->
+    Backbone.history.stop()
+    DotLedger.Routers.App::[spy] = $.noop
+    spyOn(DotLedger.Routers.App::, spy)
+    router = new DotLedger.Routers.App()
+    Backbone.history.start()
+    router
+
   it "should be defined", ->
     expect(DotLedger.Routers.App).toBeDefined()
+
+  describe 'Root', ->
+    it "routes to root", ->
+      router = createRouter('root')
+      router.navigate('/', navigateOptions)
+      expect(router.root).toHaveBeenCalled()
+
+  describe 'Accounts', ->
+    it "routes to newAccount", ->
+      router = createRouter('newAccount')
+      router.navigate('/accounts/new', navigateOptions)
+      expect(router.newAccount).toHaveBeenCalled()
+
+    it "routes to sortAccount", ->
+      router = createRouter('sortAccount')
+      router.navigate('/accounts/42/sort', navigateOptions)
+      expect(router.sortAccount).toHaveBeenCalledWith('42')
+
+    it "routes to editAccount", ->
+      router = createRouter('editAccount')
+      router.navigate('/accounts/42/edit', navigateOptions)
+      expect(router.editAccount).toHaveBeenCalledWith('42')
+
+    it "routes to importAccount", ->
+      router = createRouter('newStatement')
+      router.navigate('/accounts/42/import', navigateOptions)
+      expect(router.newStatement).toHaveBeenCalledWith('42')
+
+    it "routes to showAccout", ->
+      router = createRouter('showAccount')
+      router.navigate('/accounts/42', navigateOptions)
+      expect(router.showAccount).toHaveBeenCalledWith('42')
+
+    it "routes to showAccout with a tab", ->
+      router = createRouter('showAccount')
+      router.navigate('/accounts/42/sorted', navigateOptions)
+      expect(router.showAccount).toHaveBeenCalledWith('42', 'sorted')
+
+    it "routes to showAccout with a tab and page number", ->
+      router = createRouter('showAccount')
+      router.navigate('/accounts/42/sorted/page-13', navigateOptions)
+      expect(router.showAccount).toHaveBeenCalledWith('42', 'sorted', '13')
+
+  describe 'Categories', ->
+    it "routes to listCategories", ->
+      router = createRouter('listCategories')
+      router.navigate('/categories', navigateOptions)
+      expect(router.listCategories).toHaveBeenCalled()
+
+    it "routes to newCategory", ->
+      router = createRouter('newCategory')
+      router.navigate('/categories/new', navigateOptions)
+      expect(router.newCategory).toHaveBeenCalled()
+
+    it "routes to editCategory", ->
+      router = createRouter('editCategory')
+      router.navigate('/categories/42/edit', navigateOptions)
+      expect(router.editCategory).toHaveBeenCalledWith('42')
+
+  describe 'SortingRules', ->
+    it "routes to listSortingRules", ->
+      router = createRouter('listSortingRules')
+      router.navigate('/sorting-rules', navigateOptions)
+      expect(router.listSortingRules).toHaveBeenCalled()
+
+    it "routes to listSortingRules with page number", ->
+      router = createRouter('listSortingRules')
+      router.navigate('/sorting-rules/page-13', navigateOptions)
+      expect(router.listSortingRules).toHaveBeenCalledWith('13')
+
+    it "routes to newSortingRule", ->
+      router = createRouter('newSortingRule')
+      router.navigate('/sorting-rules/new', navigateOptions)
+      expect(router.newSortingRule).toHaveBeenCalled()
+
+    it "routes to editSortingRule", ->
+      router = createRouter('editSortingRule')
+      router.navigate('/sorting-rules/42/edit', navigateOptions)
+      expect(router.editSortingRule).toHaveBeenCalledWith('42')
+
+  describe 'Goals', ->
+    it "routes to listGoals", ->
+      router = createRouter('listGoals')
+      router.navigate('/goals', navigateOptions)
+      expect(router.listGoals).toHaveBeenCalled()
+
+  describe 'Payments', ->
+    it "routes to listPayments", ->
+      router = createRouter('listPayments')
+      router.navigate('/payments', navigateOptions)
+      expect(router.listPayments).toHaveBeenCalled()
+
+    it "routes to newPayment", ->
+      router = createRouter('newPayment')
+      router.navigate('/payments/new', navigateOptions)
+      expect(router.newPayment).toHaveBeenCalled()
+
+    it "routes to editPayment", ->
+      router = createRouter('editPayment')
+      router.navigate('/payments/42/edit', navigateOptions)
+      expect(router.editPayment).toHaveBeenCalledWith('42')
+
+  describe 'Search', ->
+    it "routes to search", ->
+      router = createRouter('search')
+      router.navigate('/search', navigateOptions)
+      expect(router.search).toHaveBeenCalled()
+
+    it "routes to search with params", ->
+      router = createRouter('search')
+      router.navigate("/search/~(foo~'bar~baz~42)", navigateOptions)
+      expect(router.search).toHaveBeenCalledWith("~(foo~'bar~baz~42)")
+
+    it "routes to search with params and page number", ->
+      router = createRouter('search')
+      router.navigate("/search/~(foo~'bar~baz~42)/page-13", navigateOptions)
+      expect(router.search).toHaveBeenCalledWith("~(foo~'bar~baz~42)", '13')


Remove test files clutter.

Remove test files clutter.

--- src/fs_utils/generated_file.coffee
+++ src/fs_utils/generated_file.coffee
@@ -5,23 +5,7 @@
 sysPath = require 'path'
 async = require 'async'
 common = require './common'
-
-getTestFiles = (config) ->
-  isTestFile = (generatedFile) ->
-    generatedFile.lastIndexOf(sysPath.normalize('test/'), 0) is 0 and
-    generatedFile.lastIndexOf('vendor') is -1
-
-  joinPublic = (generatedFile) ->
-    sysPath.join(config.paths.public, generatedFile)
-
-  joinTo = config.files.javascripts.joinTo
-  files = if typeof joinTo is 'string' then [joinTo] else Object.keys(joinTo)
-  files.filter(isTestFile).map(joinPublic)
-
-cachedTestFiles = null
-
-findTestFiles = (config) ->
-  cachedTestFiles ?= getTestFiles config
+{concatSourceMaps} = require './source-map'

 sortAlphabetically = (a, b) ->
   if a < b
@@ -120,19 +104,6 @@
   sortByConfig(paths, order).map (path) ->
     indexes[path]

-loadTestFiles = (files, testsConvention) ->
-  files
-    .map (file) ->
-      file.path
-    .filter (path) ->
-      testsConvention path
-    .map (path) ->
-      path = path.replace RegExp('\\\\', 'g'), '/'
-      path.substring 0, path.lastIndexOf '.'
-    .map (path) ->
-      "window.require('#{path}');"
-    .join('\n') + '\n'
-
 # File which is generated by brunch from other files.
 module.exports = class GeneratedFile
   #
@@ -146,23 +117,19 @@
     else
       'stylesheet'
     @minifier = minifiers.filter((minifier) => minifier.type is @type)[0]
-    @isTestFile = @path in findTestFiles @config
     Object.freeze this

   # Private: Collect content from a list of files and wrap it with
   # require.js module definition if needed.
   # Returns string.
-  _join: (files, callback) ->
+  _join: (files) ->
     debug "Joining files '#{files.map((file) -> file.path).join(', ')}'
  to '#{@path}'"
-    requireFiles = => loadTestFiles files, @config._normalized.conventions.tests
     joined = files.map((file) -> file.cache.data).join('')
-    process.nextTick =>
-      if @type is 'javascript'
-        data = @config._normalized.modules.definition(@path, joined) + joined
-        callback null, (if @isTestFile then data + requireFiles() else data)
-      else
-        callback null, joined
+    if @type is 'javascript'
+      @config._normalized.modules.definition(@path, joined) + joined
+    else
+      joined

   # Private: minify data.
   #
@@ -173,7 +140,7 @@
   _minify: (data, callback) ->
     if @config.optimize
       minify = @minifier?.optimize ? @minifier?.minify
-      minify? data, @path, callback
+      minify data, @path, callback
     else
       callback null, data

@@ -184,8 +151,7 @@
   #
   # Returns nothing.
   write: (callback) ->
-    @_join (sort @sourceFiles, @config), (error, joined) =>
+    joined = @_join (sort @sourceFiles, @config)
+    @_minify joined, (error, data) =>
       return callback error if error?
-      @_minify joined, (error, data) =>
-        return callback error if error?
-        common.writeFile @path, data, callback
+      common.writeFile @path, data, callback


left debugging on

left debugging on

--- public/javascripts/teams.coffee
+++ public/javascripts/teams.coffee
@@ -4,7 +4,6 @@
       navSelector: '.more'
       nextSelector: '.more a'
       itemSelector: 'ul.teams > li'
-      debug: true
       loading:
         img: '/images/spinner.gif'
         msgText: ''


Fix home page project avatars

Fix home page project avatars

--- app/pages/home.cjsx
+++ app/pages/home.cjsx
@@ -89,7 +89,9 @@
           if projects?
             <div className="featured-projects-list">
             {for project in projects
-              <OwnedCard key={project.id} resource={project} linkTo="project-home" translationObjectName="projectsPage" imagePromise={project.get 'avatar'}/>
+              avatarSrc = project.get('avatar').then (avatar) ->
+                avatar.src
+              <OwnedCard key={project.id} resource={project} linkTo="project-home" translationObjectName="projectsPage" imagePromise={avatarSrc} />
             }
             </div>
         }</PromiseRenderer>


Update jquery-latests version

Update jquery-latests version

--- src/configs/hash-config.coffee
+++ src/configs/hash-config.coffee
@@ -64,7 +64,7 @@
 			'cdnjs.cloudflare.com/ajax/libs/jquery/$version$/jquery.min.js'
 			'code.jquery.com/jquery-$version$.min.js'
 		]
-		file: 'jquery/1.9.1/jquery.min.js'
+		file: 'jquery/1.11.1/jquery.min.js'

 	'jquery-migrate':
 		versions: ['1.2.1']
@@ -170,6 +170,12 @@
 		]
 		file: 'google/plusone.js'

+	'plusone-new':
+		urls: [
+			'apis.google.com/js/platform.js'
+		]
+		file: 'google/platform.js'
+
 	'twitter-client':
 		urls: [
 			'platform.twitter.com/js/tfw/hub/client.js'


InterpolatedPointLayout now logs nicer in the domConsole

--- src/art/engine/layout/interpolated_point_layout.coffee
+++ src/art/engine/layout/interpolated_point_layout.coffee
@@ -41,6 +41,15 @@

     toString: -> "(#{@layout1} * #{@p} + #{@layout2} * #{1 - @p})"

+    @getter inspectedString: -> @toString()
+
+    inspect: (inspector)->
+      v = @inspect2()
+      inspector?.put v
+      v
+
+    inspect2: -> @toString()
+
     @interpolate1D: interpolate1D = (p, hasFrom, hasTo, from, to) ->
       if hasFrom
         if hasTo


Adding support for fair booths

Adding support for fair booths

--- apps/show/client/index.coffee
+++ apps/show/client/index.coffee
@@ -46,7 +46,21 @@
   city = _.findWhere(Cities, name: show.formatCity())

   if show.isFairBooth()
-    console.log show.fairName()
+    # how to get booths from a fair?
+    relatedShows = new PartnerShows
+    relatedShows.fetch
+      data:
+        # fair: show.related().fair.get('id')
+        size: 4
+        displayable: true
+        at_a_fair: true
+      error: (collection, response) =>
+        res.error
+      success: =>
+        relatedShowsView = new RelatedShowsView
+          collection: relatedShows
+          title: "More Booths from #{show.related().fair.get('name')}"
+        $('.related-shows').append relatedShowsView.$el
   else
     relatedShows = new PartnerShows
     featuredShows = new PartnerShows


Fix require() for Paw 2.2.3

Fix require() for Paw 2.2.3

--- WordPressCodeGenerator.coffee
+++ WordPressCodeGenerator.coffee
@@ -1,4 +1,10 @@
-require "mustache.js"
+# in API v0.2.0 and below (Paw 2.2.2 and below), require had no return value
+((root) ->
+  if root.bundle?.minApiVersion('0.2.0')
+    root.Mustache = require("./mustache")
+  else
+    require("mustache.js")
+)(this)

 addslashes = (str) ->
     ("#{str}").replace(/[\\']/g, '\\$&')


youtubeShell: announce 'PlayerView:Ready'

--- coffee/src/shells/youtube.shell.coffee
+++ coffee/src/shells/youtube.shell.coffee
@@ -196,6 +196,9 @@
         else
           @player.cueVideoById(@model.youtubeId(), start)

+        @trigger 'PlayerView:Ready'
+
+
       onStateChange: (event) =>
         @trigger 'PlayerView:StateChange', @, @player.getPlayerState()


afk I guess?

afk I guess?

--- bot.coffee
+++ bot.coffee
@@ -20,8 +20,10 @@
 roomStaff = []
 cycleLimits = [5,10]
 cycleLimits = [1,2]
-afkLimit = 60 * 1000 * 60
+enforceAFKAtHowManyDJs = 5
+afkLimit = 60 * 60 * 1000
 mehLimit = 5
+bootTime = Date.now()
 lastUserChats = {}
 bot.on('connected', () ->
     bot.joinRoom(ROOM, (data) ->
@@ -79,6 +81,8 @@
     clearTimeout songLengthLimitWarnTimeout
     clearTimeout autoSkipTimeout
     #sconsole.log data.djs[0]
+    if data.djs.length >= enforceAFKAtHowManyDJs
+        enforceAFK(data.djs)
     songLengthRelaxing = false
     if typeof data.media is 'undefined' or data.media is null
         return
@@ -108,6 +112,28 @@
     #else if djsInLine <= cycleLimits[0]

 )
+enforceAFK = (djs) ->
+    if djs.length < 2
+        return
+    time = Date.now()
+    minActionTime = time - afkLimit
+    checkIfCurDJStillAFK(djs[0].user,minActionTime)
+    checkIfOnDeckAFK(djs[1].user,minActionTime)
+checkIfCurDJStillAFK = (dj, timeLimit) ->
+    if typeof lastUserChats[dj.id] is 'undefined'
+        lastUserChats[dj.id] = Date.now()
+        return
+    lastChat = lastUserChats[dj.id]
+    if lastChat is -1
+        bot.chat "@"+dj.username + " please stay active to dj"
+        bot.moderateRemoveDJ(currentDJ.id,"Too long")
+checkIfOnDeckAFK = (dj, timeLimit) ->
+    if typeof lastUserChats[dj.id] is 'undefined'
+        return
+    lastChat = lastUserChats[dj.id]
+    if lastChat < timeLimit
+        bot.chat "@" + dj.username + " are you still there? You are on deck! Please chat to ensure you are active!"
+        lastUserChats[dj.id] = -1
 bot.on('voteUpdate', (data) ->
     curVotes[data.id] = data.vote
     numMehs = 0


DevTools: AppController: use provided machine object in DevToolsMainView

--- client/DevTools/AppController.coffee
+++ client/DevTools/AppController.coffee
@@ -55,7 +55,9 @@

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

-    options.view    = new DevToolsMainView
+    { machine }     = options.params
+    options.machine = machine
+    options.view    = new DevToolsMainView { machine }
     options.appInfo =
       name     : "DevTools"
       type     : "application"
@@ -64,7 +66,9 @@

   # FIXME facet, to make it work I had to call notifyWindowResizeListeners here
   handleQuery:->
-    {workspace, _currentMode} = @getView()
+
+    return unless view = @getView()
+    {workspace, _currentMode} = view

     workspace.ready ->
       wc = KD.getSingleton("windowController")
@@ -89,4 +93,4 @@
       JSEditor.loadFile  "#{app.fullPath}/index.coffee"
       CSSEditor.loadFile "#{app.fullPath}/resources/style.css"

-      view.switchMode 'develop'+      view.switchMode 'develop'


Allow compose editor to auto-resize

Allow compose editor to auto-resize

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -538,7 +538,7 @@

         if @props.composeInHTML
             div
-                className: "rt-editor form-control #{folded}",
+                className: "form-control rt-editor #{folded}",
                 ref: 'html',
                 contentEditable: true,
                 onKeyDown: @onKeyDown,


Add search params.

Add search params.

--- client/app/controllers/navbar.coffee
+++ client/app/controllers/navbar.coffee
@@ -47,6 +47,9 @@
     @navigationItems.removeClass "active"
     if route.path == "/charts"
       @chartsLink.parent("li").addClass("active")
+    else if route.path == "/search/:q"
+      match = route.route.exec(path)
+      @searchInput.val(match[1])

   onSearchInputSearch: (event) =>
     q = event.target.value.trim()


dataset rename click handling

dataset rename click handling

--- client/code/view/dataset/tile.coffee
+++ client/code/view/dataset/tile.coffee
@@ -10,6 +10,7 @@
   events:
     'click .hide': 'hideDataset'
     'click .dropdown-menu a': 'dropdownMenuItemClick'
+    'click .rename-dataset': 'renameDatasetClick'

   initialize: ->
     @model.on 'change', @render, this
@@ -31,4 +32,12 @@

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


Tool pages show full screen

Tool pages show full screen

--- client/code/view/view/content.coffee
+++ client/code/view/view/content.coffee
@@ -6,6 +6,14 @@
     @settings (settings) =>
       frag = encodeURIComponent JSON.stringify(settings)
       @setupEasyXdm "#{@boxUrl}/#{@model.get 'box'}/#{settings.source.publishToken}/container.html##{frag}"
+
+  render: ->
+    $('body').addClass('fullscreen')
+    super()
+
+  close: ->
+    $('body').removeClass('fullscreen')
+    super()

   setupEasyXdm: (url) ->
     transport = new easyXDM.Rpc


SHow unknown warning even to users with only was

--- client/lib/needUnknownWarning.coffee
+++ client/lib/needUnknownWarning.coffee
@@ -1,2 +1,5 @@
+objectHasKeys = (object) ->
+    return object? && Object.keys(object)?.length
+
 export default needUnknownWarning = (myUser) ->
-    return myUser && myUser.userList == "unknown" && (myUser.rating > 0 || (myUser.byWeek?.ok? && Object.keys(myUser.byWeek?.ok)?.length))
+    return myUser && myUser.userList == "unknown" && (myUser.rating > 0 || objectHasKeys(myUser.byWeek?.ok) || objectHasKeys(myUser.byWeek?.solved))


Routes: Handle machine label only route.

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -75,6 +75,8 @@

     '/:name?/IDE': -> routeToLatestWorkspace()

+    '/:name?/IDE/:machineLabel': -> routeToLatestWorkspace()
+
     '/:name?/IDE/:machineLabel/:workspaceSlug': (data) ->

       { machineLabel, workspaceSlug } = data.params


cleanup a bit

cleanup a bit

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -78,7 +78,7 @@
 # add a source handler for config files so that they are watched for changes
 Plugin.registerSourceHandler 'browserify.options.json', ->

-checkFileChanges = (step) ->
+checkFileChanges = (step, cacheFileName) ->
   # we must rebuild when:
   #  1. we don't have cached results
   #  2. files have changed which may alter the build:
@@ -92,22 +92,27 @@
   #  2. have the cached source map file

   # if we don't have cached results then we rebuild (return true)
-  unless fs.existsSync(step.fullInputPath + '.cached') and
+  unless fs.existsSync(cacheFileName) and
     fs.existsSync(step.fullInputPath + '.map')
       return true

-  cachedTime = fs.statSync(step.fullInputPath + '.cached').mtime.getTime()
+  # get the modified time of the cache file as an integer
+  # NOTE: could use created time...
+  cachedTime = fs.statSync(cacheFileName).mtime.getTime()

   # if any of the files has changed then we rebuild (return true)
   for file in getFilesToCheck(step)
-    # if it's a different time, then we rebuild (return true)
+    # if the file exists (options file may not exist)
     if fs.existsSync(file)
+      # get the modified time as an integer
       modifiedTime = fs.statSync(file).mtime.getTime()
+      # if it's a different time, then we rebuild (return true)
       if cachedTime < modifiedTime
         return true

   # no changes so we can use the cached version (return false)
   return false
+
 checkFilename = (step) ->

   if step.inputPath is 'browserify.js'
@@ -223,7 +228,7 @@
 # get compile result via cache or compiling
 getResult = (step, bundle) ->
   cacheFileName = step.fullInputPath + '.cached'
-  compileChanges = checkFileChanges step
+  compileChanges = checkFileChanges step, cacheFileName

   if compileChanges
     # call our wrapped function with the readable stream as its argument


KDButtonMenu: Fix menu placement when scrolled

Take into account the current window scroll position when determining
the context menu placement.

--- components/buttons/KDButtonMenu.coffee
+++ components/buttons/KDButtonMenu.coffee
@@ -21,13 +21,15 @@
     # options       = @getOptions()
     button        = @getDelegate()
     mainHeight    = $(window).height()
+    mainScroll    = $(window).scrollTop()
     buttonHeight  = button.getHeight()
     buttonWidth   = button.getWidth()
     top           = button.getY() + buttonHeight
     menuHeight    = @getHeight()
     menuWidth     = @getWidth()

-    ghostCss = if top + menuHeight > mainHeight
+    # Determine if the menu will be off screen or not
+    ghostCss = if top + menuHeight > mainHeight + mainScroll
       top = button.getY() - menuHeight
       @setClass "top-menu"
       top     : "100%"


Marked XML attributes.

Marked XML attributes.

--- records.coffee
+++ records.coffee
@@ -24,7 +24,6 @@
     type:   "email"
     desc:   "E–Mail"
    ,
-    name:   "password"
     type:   "password"
     desc:   "Password"
    ,
@@ -78,16 +77,19 @@
       name: "scaling"
       type: "percent"
       desc: "Skalierung"
+      attr: true
      ,
       name: "xpos"
       type: "pixel"
       value: 0
       desc: "X–Verschiebung"
+      attr: true
      ,
       name: "ypos"
       type: "pixel"
       value: 0
       desc: "Y–Verschiebung"
+      attr: true
     ]
    ,
     name: "display"
@@ -105,62 +107,77 @@
         "768 x 1366 px"
       ]
       desc: "Auflösung"
+      attr: true
      ,
       name: "autostart_delay"
       type: "seconds"
       desc: "Autom. Autostart der App nach"
+      attr: true
      ,
       name: "autorestart_delay"
       type: "minutes"
       desc: "App geht zum Startbildschirm nach"
+      attr: true
      ,
       name: "clipboard_doubletap"
       type: "radio"
       desc: "Doppeltippen öffnet Ablage"
+      attr: true
      ,
       name: "new_iconset"
       type: "radio"
       desc: "Neues Icon–Set"
+      attr: true
      ,
       name: "color"
       type: "color"
       desc: "Highlight–Farbe"
+      attr: true
      ,
       name: "scaling"
       type: "percent"
       desc: "Skalierung"
+      attr: true
      ,
       name: "scaling_min"
       type: "percent"
       desc: "Skalierung min"
+      attr: true
      ,
       name: "scaling_max"
       type: "percent"
       desc: "Skalierung max"
+      attr: true
      ,
       name: "xpos"
       type: "pixel"
       desc: "Startkoordinate X"
+      attr: true
      ,
       name: "ypos"
       type: "pixel"
       desc: "Startkoordinate Y"
+      attr: true
      ,
       name: "do_hide_pi"
       type: "radio"
       desc: "PI verstecken?"
+      attr: true
      ,
       name: "do_display_errors"
       type: "radio"
       desc: "Display–Errors?"
+      attr: true
      ,
       name: "have_mouse"
       type: "radio"
       desc: "Maus?"
+      attr: true
      ,
       name: "have_fixed_navigation"
       type: "radio"
       desc: "Fixierte Navigation unten?"
+      attr: true
     ]
    ,
     name: "direct_mail"
@@ -170,10 +187,12 @@
       name: "enable"
       type: "radio"
       desc: "Aktiviert?"
+      attr: true
      ,
       name: "enable_cc"
       type: "radio"
       desc: "CC–E–Mail anzeigen?"
+      attr: true
      ,
       name: "policy"
       type: "text"
@@ -195,10 +214,12 @@
       name: "enable"
       type: "radio"
       desc: "Aktiviert?"
+      attr: true
      ,
       name: "interval"
       type: "seconds"
       desc: "Intervall in Sekunden"
+      attr: true
     ]
    ,
     name: "update"
@@ -208,10 +229,12 @@
       name: "do_background_update"
       type: "radio"
       desc: "Update im Hintergrund?"
+      attr: true
      ,
       name: "update_interval"
       type: "seconds"
       desc: "Update–Interval in Sekunden"
+      attr: true
     ]
    ,
     name: "email"
@@ -267,17 +290,17 @@
     type: "textline"
     desc: "Beschriftung"
    ,
-    name: "ad"
-    type: "selection"
-    desc: "Werbung"
+    type: "ad"
    ,
     name: "is_active"
     type: "radio"
     desc: "aktiv?"
+    attr: true
    ,
     name: "is_global"
     type: "radio"
     desc: "global?"
+    attr: true
    ,
     name: "code"
     type: "password"
@@ -285,25 +308,29 @@
   ]

   media: [
-    name: "file"
-    type: "file"
-    desc: "Datei"
+    name: "src"
+    type: "url"
+    attr: true
    ,
     name: "x"
     type: "pixel"
     desc: "X–Position"
+    attr: true
    ,
     name: "y"
     type: "pixel"
     desc: "Y–Position"
+    attr: true
    ,
     name: "scaling"
     type: "percent"
     desc: "Skalierung"
+    attr: true
    ,
     name: "rotation"
     type: "degree"
     desc: "Drehung"
+    attr: true
   ]

   dashboard: [
@@ -317,16 +344,6 @@
   ]

   mediaviewer: [
-    name: "background"
-    type: "file"
-    desc: "Hintergrundbild"
-   ,
-    name: "items"
-    type: "file"
-    desc: "Medien"
-  ]
-
-  flippic: [
     name: "background"
     type: "file"
     desc: "Hintergrundbild"
@@ -343,13 +360,11 @@
       type: "media"
       desc: "Rückseite"
      ,
-      name: "video"
-      type: "media"
-      desc: "Video"
-    ]
-  ]
-
-  coverflow: [
+      type: "video"
+    ]
+  ]
+
+  flippic: [
     name: "background"
     type: "file"
     desc: "Hintergrundbild"
@@ -366,13 +381,11 @@
       type: "media"
       desc: "Rückseite"
      ,
-      name: "pdf"
-      type: "media"
-      desc: "PDF"
-    ]
-  ]
-
-  slidebook: [
+      type: "video"
+    ]
+  ]
+
+  coverflow: [
     name: "background"
     type: "file"
     desc: "Hintergrundbild"
@@ -389,9 +402,28 @@
       type: "media"
       desc: "Rückseite"
      ,
-      name: "pdf"
-      type: "media"
-      desc: "PDF"
+      type: "pdf"
+    ]
+  ]
+
+  slidebook: [
+    name: "background"
+    type: "file"
+    desc: "Hintergrundbild"
+   ,
+    name: "items"
+    type: "list"
+    desc: "Medien"
+    list: [
+      name: "front"
+      type: "media"
+      desc: "Vorderseite"
+     ,
+      name: "back"
+      type: "media"
+      desc: "Rückseite"
+     ,
+      type: "pdf"
     ]
   ]

@@ -403,11 +435,9 @@
     name:  "items"
     type:  "list"
     limit: 12
-    desc:  "Medien"
-    list: [
-      name: "front"
-      type: "media"
-      desc: "Vorderseite"
+    desc:  "Vorderseiten"
+    list: [
+      type: "media"
     ]
   ]

@@ -427,8 +457,8 @@
     type: "selection"
     opts: ["normal"]
     desc: "Typ"
-   ,
-    name: "location"
+    attr: true
+   ,
     type: "location"
     desc: "Kartenmitte"
    ,
@@ -446,37 +476,31 @@
     name: "has_address_field"
     type: "radio"
     desc: "Adresszeile einblenden?"
+    attr: true
    ,
     name: "has_keyboard"
     type: "radio"
     desc: "Tastatur einblenden?"
+    attr: true
    ,
     name:  "blacklist"
     type:  "list"
     desc:  "Blacklist"
     list: [
-      name: "url"
       type: "url"
-      desc: "URL"
     ]
    ,
     name:  "whitelist"
     type:  "list"
     desc:  "whitelist"
     list: [
-      name: "url"
       type: "url"
-      desc: "URL"
     ]
   ]

   wall: [
-    name:  "blacklist"
     type:  "list"
-    desc:  "Blacklist"
-    list: [
-      name: "item"
-      type: "media"
-      desc: "Medium"
-    ]
-  ]
+    list: [
+      type: "media"
+    ]
+  ]


Added basic movuino link

Added basic movuino link

--- client/src/app/io.coffee
+++ client/src/app/io.coffee
@@ -838,3 +838,36 @@
       global: options.locationInfo
     establishData: -> # NOP
     destroy: -> # NOP
+
+# Gives access to the Movuino
+RW.io.movuino =
+  meta:
+    visual: false
+  factory: (options) ->
+    # Connect to the serial port via a web socket (using MisterS)
+    state =
+      connected: false
+      a: [0, 0, 0]
+      g: [0, 0, 0]
+
+    # TODO: check for errors
+    # TODO: make host and port configurable
+    ws = new WebSocket("ws://localhost:8888/")
+    ws.onopen = ->
+      ws.send("l")
+      state.connected = true
+    ws.onmessage = (event) ->
+      # Expecting data like "l 6360 -8796 -16152 -371 218 -147"
+      tokens = event.data.split(" ")
+      state.a = _.map(tokens[1..3], parseFloat)
+      state.g = _.map(tokens[4..6], parseFloat)
+
+    return {
+      provideData: -> return global: state
+      establishData: -> # NOP
+      destroy: ->
+        if not ws then return
+
+        ws.send("q")
+        ws.close()
+    }


Drop sinon-qunit

Drop sinon-qunit

--- src/dist/batman.testing.coffee
+++ src/dist/batman.testing.coffee
@@ -1,5 +1,4 @@
 #= require ../../tests/lib/qunit
 #= require ../../tests/lib/sinon
-#= require ../../tests/lib/sinon-qunit

 #= require ../extras/batman.testing


wait til call stack is cleared before starting tool

--- app/views/ubret_tool.coffee
+++ app/views/ubret_tool.coffee
@@ -43,12 +43,7 @@
       _.extend opts, @model.settings.toJSON()

       @tool.setOpts opts
-
-      # This is Horrifically ugly
-      if $("##{@id}").length isnt 0
-        @tool.start()
-      else
-        setTimeout @tool.start, 500
+      _.defer @tool.start
     @

   dataKeys: (dataModel) =>


Add support for specifying package name

Add support for specifying package name

--- src/init.coffee
+++ src/init.coffee
@@ -100,8 +100,8 @@
         fs.removeSync(path.join(destinationPath, 'LICENSE.md'))
         callback()

-  generateFromTemplate: (packagePath, templatePath) ->
-    packageName = path.basename(packagePath)
+  generateFromTemplate: (packagePath, templatePath, packageName) ->
+    packageName = path.basename(packagePath) unless packageName?
     packageAuthor = process.env.GITHUB_USER or 'atom'

     fs.makeTreeSync(packagePath)


ProviderInterface: postCreate: it will be called when provider needs some postCreate operations

--- workers/social/lib/social/models/computeproviders/providerinterface.coffee
+++ workers/social/lib/social/models/computeproviders/providerinterface.coffee
@@ -9,6 +9,8 @@

   return message

+PASS_THROUGH = (..., callback)-> callback null
+
 module.exports = class ProviderInterface

   @ping           = NOT_IMPLEMENTED
@@ -18,3 +20,5 @@

   @fetchExisting  = NOT_IMPLEMENTED
   @fetchAvailable = NOT_IMPLEMENTED
+
+  @postCreate     = PASS_THROUGH


convert text to lower case so that test fails

--- test/integration/subscription.coffee
+++ test/integration/subscription.coffee
@@ -114,7 +114,8 @@
     it 'it does not say expired any more', (done) ->
       wd40.waitForText 'Expired Trialler', =>
         wd40.getText 'body', (err, text) =>
-          text.should.not.include 'Expired'
+          text = text.toLowerCase()
+          text.should.not.include 'expired'
           text.should.not.include 'days left'
           done()


Adjust the exact text we match against in the test

--- test/integration/subscription.coffee
+++ test/integration/subscription.coffee
@@ -115,8 +115,8 @@
       wd40.waitForText 'Expired Trialler', =>
         wd40.getText 'body', (err, text) =>
           text = text.toLowerCase()
-          text.should.not.include 'expired'
-          text.should.not.include 'days left'
+          text.should.not.match /free\s+trial:\s+expired/
+          text.should.not.match /days\s+left/
           done()

     it 'it says go to your datasets', (done) ->


bugfix

bugfix

--- src/hubot-scfes-command.coffee
+++ src/hubot-scfes-command.coffee
@@ -15,7 +15,7 @@
     msg.reply "registerd " + next_date

   robot.respond /scfes remind stop/, (msg) ->
-    if scfes.remindStop
+    if scfes.remindStop()
       msg.reply "remind stop"
     else
       msg.reply "remind stop error"


:bug: Guard against undefined args

:bug: Guard against undefined args

--- src/link.coffee
+++ src/link.coffee
@@ -28,8 +28,8 @@
   run: (options) ->
     {callback} = options
     options = @parseOptions(options.commandArgs)
-
-    linkPath = path.resolve(process.cwd(), String(options.argv._[0]) ? '.')
+    options.argv._[0] = if options.argv._[0] then String(options.argv._[0])
+    linkPath = path.resolve(process.cwd(), options.argv._[0] ? '.')
     try
       packageName = CSON.readFileSync(CSON.resolve(path.join(linkPath, 'package'))).name
     packageName = path.basename(linkPath) unless packageName


Include packages in ~/.atom/packages/dev

--- src/list.coffee
+++ src/list.coffee
@@ -14,6 +14,7 @@

   constructor: ->
     @userPackagesDirectory = path.join(config.getAtomDirectory(), 'packages')
+    @devPackagesDirectory = path.join(config.getAtomDirectory(), 'dev', 'packages')
     @bundledPackagesDirectory = path.join(config.getResourcePath(), 'src', 'packages')
     @vendoredPackagesDirectory = path.join(config.getResourcePath(), 'vendor', 'packages')
     if configPath = CSON.resolve(path.join(config.getAtomDirectory(), 'config'))
@@ -42,6 +43,7 @@
       packageLine += "@#{pack.version}" if pack.version?
       packageLine += ' (disabled)' if @isPackageDisabled(pack.name)
       packageLine
+    console.log()

   listPackages: (directoryPath) ->
     packages = []
@@ -63,6 +65,12 @@
     console.log "#{@userPackagesDirectory.cyan} (#{userPackages.length})"
     @logPackages(userPackages)

+  listDevPackages: ->
+    devPackages = @listPackages(@devPackagesDirectory)
+    if devPackages.length > 0
+      console.log "#{@devPackagesDirectory.cyan} (#{devPackages.length})"
+      @logPackages(devPackages)
+
   listNodeModulesWithAtomEngine: ->
     nodeModulesDirectory = path.join(config.getResourcePath(), 'node_modules')
     allPackages = @listPackages(nodeModulesDirectory)
@@ -77,7 +85,7 @@
     @logPackages(packages)

   run: (options) ->
+    @listBundledPackages()
+    @listDevPackages()
     @listUserPackages()
-    console.log ''
-    @listBundledPackages()
     options.callback()


better size for dialog

better size for dialog

--- src/load.coffee
+++ src/load.coffee
@@ -83,6 +83,7 @@
       $(body).dialog
         modal:   true
         buttons: [{text: 'Ok', click: -> $(this).dialog('close')}]
+        width:   400

 get_custom_level = ->


Lol reminders

Lol reminders

--- src/main.coffee
+++ src/main.coffee
@@ -60,6 +60,7 @@
         )

 else
+    console.log 'cal should fix basePath'
     console.log 'Initializing webserver'
     app = new Application(config, db)
     console.log 'Starting delete expired images task'


[skip] Focused the field you auto-typed (1m)

--- app/assets/javascripts/globals.coffee
+++ app/assets/javascripts/globals.coffee
@@ -16,6 +16,7 @@
     $el.val typeItValue.substr(0, len)
     len = len + 1
     if interval and len > typeItValue.length
+      $el.focus()
       console.log("clear interval")
       window.clearInterval(interval)


Prevent tabbing around when loading a dialog

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -261,6 +261,9 @@
     $('#dialog').find('a[href], input, select, textarea, button').
       filter(':not([disabled])').filter(':visible').first().focus()
     return false
+  else if $('#overlay').length > 0 and $('#dialog').length == 0
+    $('#overlay').focus()
+    return false

 $(document).ajaxComplete ->
   target_external_links()


Add flash message on save project.

Add flash message on save project.

--- app/coffee/controllers/project.coffee
+++ app/coffee/controllers/project.coffee
@@ -20,7 +20,7 @@
         $scope.projects = projects

-ProjectAdminController = ($scope, $rootScope, $routeParams, $data, rs) ->
+ProjectAdminController = ($scope, $rootScope, $routeParams, $data, $gmFlash, rs) ->
     $rootScope.pageSection = 'admin'
     $rootScope.pageBreadcrumb = ["", "Project Admin"]
     $rootScope.projectId = parseInt($routeParams.pid, 10)
@@ -31,7 +31,8 @@
     $scope.submit = ->
         promise = $scope.project.save()
         promise.then (data) ->
-            console.log data
+            $gmFlash.info("Project saved successful!")
+
         promise.then null, (data) ->
             console.error data
             $scope.checksleyErrors = data
@@ -39,4 +40,5 @@

 module = angular.module("greenmine.controllers.project", [])
 module.controller("ProjectListController", ['$scope', '$rootScope', 'resource', ProjectListController])
-module.controller("ProjectAdminController", ["$scope", "$rootScope", "$routeParams", "$data", "resource", ProjectAdminController])
+module.controller("ProjectAdminController", ["$scope", "$rootScope", "$routeParams", "$data",
+                                             "$gmFlash", "resource", ProjectAdminController])


Rewrite pool to use two arrays

Rewrite pool to use two arrays

--- src/pool.coffee
+++ src/pool.coffee
@@ -2,65 +2,72 @@
 * @description Object pool One possible way to store common recyclable objects
 ###
 class Pool
-  constructor: ->
-    @pool = []        # Pool objects
-    @i = 0            # Iterator
-    @active = false   # Whether pool has any active objects
-    @activeCount = 0  # Number of active objects
-    @length = 0       # Shortcut for @pool.length
+  constructor: (size) ->
+    @length = 0
+    @inactiveLength = 0
+    @activeObjects = []
+    @inactiveObjects = []
+
+    while size--
+      @activeObjects.push null
+      @inactiveObjects.push null
+
+    @tmp = 0            # Iterator

   ###
    * @description Get the next "inactive" object in the Pool
   ###
   activate: ->
-    @i = @length
-    while @i--
-      if not @pool[@i].active
-        @active = true
-        @activeCount += 1
-        @pool[@i].active = true
-        @pool[@i].activate() if typeof @pool[@i].activate is 'function'
-        return @pool[@i]
-    return null
+    if @inactiveLength > 0 && @inactiveObjects[@inactiveLength - 1] != null
+      @activeObjects[@length] = @inactiveObjects[@inactiveLength - 1]
+      @inactiveObjects[@inactiveLength - 1] = null
+      @activeObjects[@length].active = true
+      @activeObjects[@length].activate() if typeof @activeObjects[@length].activate is 'function'
+      @length += 1
+      @inactiveLength -= 1

   ###
    * @description Activate all the objects in the pool
   ###
   activateAll: ->
-    @active = true
-    @i = @length
-    while @i--
-      if not @pool[@i].active
-        @activeCount += 1
-        @pool[@i].active = true
-        @pool[@i].activate() if typeof @pool[@i].activate is 'function'
+    while @inactiveLength--
+      @activeObjects[@length] = @inactiveObjects[@inactiveLength]
+      @inactiveObjects[@inactiveLength] = null
+      @activeObjects[@length].active = true
+      @activeObjects[@length].activate() if typeof @activeObjects[@length].activate is 'function'
+      @length += 1

   ###
    * @description Deactivate an object (won't be drawn/updated)
   ###
-  deactivate: (index) ->
-    return if @pool[index] is undefined
+  deactivate: (object) ->
+    @tmp = @activeObjects.indexOf object
+    return if @tmp == -1

-    @pool[index].active = false
-    @activeCount -= 1
-    @active = false if @activeCount == 0
+    @activeObjects[@tmp].active = false
+    @inactiveObjects[@inactiveLength] = @activeObjects[@tmp]
+
+    while @tmp < @length
+      @activeObjects[@tmp] = @activeObjects[@tmp + 1]
+      @tmp += 1
+
+    @length -= 1

   ###
    * @description Deactivate all objects in pool
   ###
   deactivateAll: ->
-    @active = false
-    @i = @length
-    while @i--
-      if @pool[@i].active
-        @active -= 1
-        @pool[@i].active = false
+    while @length--
+      @inactiveObjects[@inactiveLength] = @activeObjects[@length]
+      @inactiveObjects[@inactiveLength].active = true
+      @activeObjects[@length] = null
+      @inactiveLength += 1

   ###
    * @description Convenience method to access a particular child index
   ###
   at: (index) ->
-    return @pool[index] || null
+    return @activeObjects[index] || null

   ###
    * @description Add object to one of the lists
@@ -68,21 +75,29 @@
   add: (object) ->
     throw "Pool objects need an 'active' property." if object.active is undefined

-    @pool.push(object)
-    @length += 1
+    if object.active
+      if @length + 1 > @activeObjects.length
+        @activeObjects.push null
+        @inactiveObjects.push null

-    if object.active
-      @activeCount += 1
-      @active = true
+      @activeObjects[@length] = object
+      @length += 1
+    else
+      if @inactiveLength + 1 > @inactiveObjects.length
+        @activeObjects.push null
+        @inactiveObjects.push null
+
+      @inactiveObjects[@inactiveLength] = object
+      @inactiveLength += 1

   ###
    * @description "Passthrough" method which updates active child objects
   ###
   update: (delta) ->
-    @i = @length
-    while @i--
-      @pool[@i].update delta
-      @deactivate @i if not @pool[@i].active
+    @tmp = @length
+    while @tmp--
+      @activeObjects[@tmp].update delta
+      @deactivate @tmp if not @activeObjects[@tmp].active

   ###
    * @description "Passthrough" method which draws active child objects
@@ -91,8 +106,8 @@
     offsetX = offsetX || 0
     offsetY = offsetY || 0

-    @i = @length
-    while @i--
-      @pool[@i].draw context, offsetX, offsetY
+    @tmp = @length
+    while @tmp--
+      @activeObjects[@tmp].draw context, offsetX, offsetY

-module.exports = Pool+module.exports = Pool


Fixing copy/paste mistake

Fixing copy/paste mistake

--- src/real.coffee
+++ src/real.coffee
@@ -35,10 +35,8 @@
   robot.hear /real(c|C)offee/i, (msg) ->
     msg.send msg.random coffeePics

-module.exports = (robot) ->
   robot.hear /real(d|D)rink/i, (msg) ->
     msg.send msg.random drinkPics

-module.exports = (robot) ->
   robot.hear /real(w|W)ine/i, (msg) ->
     msg.send msg.random winePics


root: remove hot reload

root: remove hot reload

--- src/root.coffee
+++ src/root.coffee
@@ -43,10 +43,3 @@
   window.addEventListener 'load', init
 else
   init()
-
-#############################
-# ENABLE WEBPACK HOT RELOAD #
-#############################
-
-if module.hot
-  module.hot.accept()


TaggedNodeCollection#unscrub now accepts objects, and unscrubs in place.

--- src/tagged_node_collection.coffee
+++ src/tagged_node_collection.coffee
@@ -45,6 +45,7 @@
         @ids[id] = node

     # Substitutes DOM elements in a parameter list with their id.
+    # TODO: do this in place
     scrub : (params) ->
         scrubbed = []
         for param in params
@@ -56,17 +57,20 @@
                 scrubbed.push param
         scrubbed

-    # Need to add support for scrubbing properties of an object (recursively)
+    # Replace nodeIDs with nodes in arrays or objects (shallow).
+    # Updates are done in place.
     unscrub : (params) ->
         if params instanceof Array
-            unscrubbed = []
-            for param in params
+            for param, index in params
                 if (typeof param == 'string') && /^node\d+$/.test(param)
-                    unscrubbed.push(@get(param))
-                else
-                    unscrubbed.push(param)
-            return unscrubbed
+                    params[index] = @get(param)
+            return params
+        else if params instanceof Object
+            for key, value of params
+                if (typeof value == 'string') && /^node\d+$/.test(value)
+                    params[key] = @get(value)
+            return params
         else
-            throw new Error "params must be an array"
+            throw new Error "params must be an array or object"

 module.exports = TaggedNodeCollection


Converted Balloon to Coffeescript class

Converted Balloon to Coffeescript class

--- js-library/app/js/views/balloon.coffee
+++ js-library/app/js/views/balloon.coffee
@@ -1,52 +1,49 @@
-Factlink.Balloon = (factId, fact) ->
-  el = null
-  mouseOutTimeoutID = null
-  loadingTimeoutID = null
-  loading = false
+class Factlink.Balloon
+  constructor: (factId, fact) ->
+    @_mouseOutTimeoutID = null
+    @_loadingTimeoutID = null
+    @_loading = false
+    @_initializeTemplate()

-  initializeTemplate = ->
-    el = $(Factlink.templates.indicator)
-    el.appendTo(Factlink.el)
-    el.bind "mouseenter", -> fact.focus()
-    el.bind "mouseleave", -> fact.blur()
-    el.bind "click", -> fact.click()
+  _initializeTemplate: ->
+    @$el = $(Factlink.templates.indicator)
+    @$el.appendTo(Factlink.el)
+    @$el.bind "mouseenter", -> fact.focus()
+    @$el.bind "mouseleave", -> fact.blur()
+    @$el.bind "click", -> fact.click()

-  hideAll = ->
-    el.closest("#fl").find(".fl-popup").hide()
+  _hideAll: ->
+    @$el.closest("#fl").find(".fl-popup").hide()

-  initializeTemplate()
+  show: (top, left, fast) ->
+    window.clearTimeout @_mouseOutTimeoutID
+    if fast
+      @_hideAll()
+      @$el.show()
+    else
+      @_mouseOutTimeoutID = window.setTimeout (=> @_hideAll(); @$el.fadeIn "fast"), 200

-  @show = (top, left, fast) ->
-    window.clearTimeout mouseOutTimeoutID
-    if fast
-      hideAll()
-      el.show()
-    else
-      mouseOutTimeoutID = window.setTimeout (-> hideAll(); el.fadeIn "fast"), 200
+    Factlink.set_position_of_element top, left, window, @$el

-    Factlink.set_position_of_element top, left, window, el
-
-  @hide = (callback) ->
-    window.clearTimeout mouseOutTimeoutID
-    el.fadeOut "fast", callback
+  hide: (callback) ->
+    window.clearTimeout @_mouseOutTimeoutID
+    @$el.fadeOut "fast", callback
     fact?.stopHighlighting()

-  @isVisible = ->
-    el.is ":visible"
+  isVisible: ->
+    @$el.is ":visible"

-  @destroy = ->
-    el.remove()
+  destroy: ->
+    @$el.remove()

-  @startLoading = ->
-    loading = true
-    loadingTimeoutID = setTimeout (=> @stopLoading()), 17000
-    el.addClass "fl-loading"
+  startLoading: ->
+    @_loading = true
+    @_loadingTimeoutID = setTimeout (=> @stopLoading()), 17000
+    @$el.addClass "fl-loading"

-  @stopLoading = ->
-    window.clearTimeout loadingTimeoutID
-    loading = false
-    @hide -> el.removeClass "fl-loading"
+  stopLoading: ->
+    window.clearTimeout @_loadingTimeoutID
+    @_loading = false
+    @hide => @$el.removeClass "fl-loading"

-  @loading = -> loading
-
-  return this
+  loading: -> @_loading


on failed search clear result

on failed search clear result

--- src/main/javascript/dss/dss.coffee
+++ src/main/javascript/dss/dss.coffee
@@ -51,7 +51,9 @@
     if $scope.query == ''
       $rootScope.$apply -> d.searchResult = false for d in $scope.index
     else if $scope.query != lastQuery
-      dss.searchHost (domain: $scope.query), (r) ->
+      dss.searchHost((domain: $scope.query), (r) ->
         d.searchResult = d.id == r.id for d in $scope.index
+      , ->
+        d.searchResult = false for d in $scope.index)
     lastQuery = $scope.query
   , 500)


Reflect backend party search defaults in js

--- app/assets/site/search.coffee
+++ app/assets/site/search.coffee
@@ -18,7 +18,7 @@
       if $scope.type == 'party'
         if params.institution == 'true'
           $scope.type = 'institution'
-        else if `params.access == constants.permission.EDIT`
+        else if `params.access == constants.permission.EDIT` || $.isEmptyObject(params)
           $scope.type = 'principal'
         else if `params.access == constants.permission.READ`
           $scope.type = 'affiliate'


sends coupon with charge

sends coupon with charge

--- apps/tools/client/premium_view.coffee
+++ apps/tools/client/premium_view.coffee
@@ -5,11 +5,14 @@
 analytics = require '../../../lib/analytics.coffee'

 module.exports = class PremiumView extends Backbone.View
+  price: 4500

   events:
     'click .button--add': 'openCheckout'

   initialize: ->
+    @price = 2750 if sd.COUPON
+
     @handler = StripeCheckout.configure
       key: sd.STRIPE_PUBLISHABLE_KEY
       image: 'https://s3.amazonaws.com/stripe-uploads/acct_14Lg6j411YkgzhRMmerchant-icon-1432502887007-arena-mark.png'
@@ -21,7 +24,7 @@
     @handler.open
       name: 'Are.na'
       description: '1 year / Premium subscription'
-      amount: 4500
+      amount: @price

     @$('.premium--status').addClass('is-active')

@@ -32,7 +35,9 @@
     $.ajax
       url: "#{sd.API_URL}/charges"
       type: 'POST'
-      data: stripeToken: token
+      data:
+        stripeToken: token
+        coupon: sd.COUPON
       success: (response) =>
         @$('.premium--status').addClass('is-successful')
         @$('.premium--status .inner').text "Registration successful."


Remove dead links

Remove dead links

--- server/materials/data/topics/cartesian_tree.coffee
+++ server/materials/data/topics/cartesian_tree.coffee
@@ -12,8 +12,8 @@
             ruen("Декартово дерево", "Cartesian tree (treap)"),
             ruen("Задачи на декартовы деревья", "Problems on cartesian trees"),
         [label(ruen(
-             "
Для начала почитайте про двоичные деревья поиска, ну или еще здесь. Вам не обязательно (пока) уметь их писать, но просто поймите, что это такое и с чем его едят.
\n
Теория на e-maxx
\nТеория на сайте ИТМО
\nЕще какая-то теория
\nЕще какая-то теория
\nМожете еще погуглить\n
",
-             "
To begin with, read about binary search trees, well, or else here. You don't have to (yet) be able to write them, but just understand what it is and how it works.
\n
Theory on e-maxx
\nTheory on the ITMO website
\nSome other theory
\nSome other theory
\nYou can still Google\n
")),
+             "
Для начала почитайте про двоичные деревья поиска, ну или еще здесь. Вам не обязательно (пока) уметь их писать, но просто поймите, что это такое и с чем его едят.
\n
Теория на e-maxx
\nТеория на сайте ИТМО
\nМожете еще погуглить\n
",
+             "
To begin with, read about binary search trees, well, or else here. You don't have to (yet) be able to write them, but just understand what it is and how it works.
\n
Theory on e-maxx
\nTheory on the ITMO website
\nYou can still Google\n
")),
             problem(2782),
             problem(1363),
             problem(2790),


update subject-set-viewer.cjsx to allow users to click on page image thumbnails even after completing the first task of a mark workflow

--- app/assets/javascripts/components/subject-set-viewer.cjsx
+++ app/assets/javascripts/components/subject-set-viewer.cjsx
@@ -41,10 +41,13 @@

   specificSelection: (blah, new_index) ->
     # this prevents navigating away from the subject during a workflow --AMS
-    if @props.workflow.first_task == @props.task.key
-      @props.onViewSubject? new_index
-    else
-      return null
+    # NB: By commenting out the following block and always sending users on
+    # to a new page upon click, we can enable multipage traversal -DD
+    #if @props.workflow.first_task == @props.task.key
+    #  @props.onViewSubject? new_index
+    #else
+    #  return null
+    @props.onViewSubject? new_index

   onToolbarExpand: ->
     @setState toolbar_expanded: true


Fix client on Firefox 34.0 beta

Fix client on Firefox 34.0 beta

Our ajax function was passing params as object to xhr.send function.
For some reason this breaks on Firefox 34.0 beta. Sending stringified
version of params works correctly.

--- assets/scripts/lib/travis/ajax.coffee
+++ assets/scripts/lib/travis/ajax.coffee
@@ -27,6 +27,9 @@
     !publicEndpoint || privateEndpoint

   ajax: (url, method, options) ->
+    # TODO: we have our own ajax implementation, because jQuery didn't
+    #       properly worked with headers on firefox, it would be nice to check
+    #       if this is still a problem and if we can remove this
     method = method || "GET"
     method = method.toUpperCase()

@@ -135,6 +138,10 @@
           reject(xhr)
           options.error.call(data, xhr.status, xhr)

-    xhr.send(options.data)
+    data = options.data
+    if typeof(options.data) == "object" && (Ember.isNone(options.contentType) || options.contentType.match /application\/json/)
+      data = JSON.stringify(data)
+
+    xhr.send(data)

     return promise


Handle null user in autosubscribe

Handle null user in autosubscribe

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -63,7 +63,7 @@
   the default is whether they are globally autosubscribed, and if that
   hasn't been specified either, the default is true.
   ###
-  auto = user.profile?.notifications?.autosubscribe
+  auto = user?.profile?.notifications?.autosubscribe
   if auto?
     (auto[escapeGroup group] ? auto[wildGroup]) != false
   else


Bug fix for undefined parameters

Bug fix for undefined parameters

--- lib/relations/one.coffee
+++ lib/relations/one.coffee
@@ -217,14 +217,14 @@
     # return VirtualCursor(query, {model: model, relation: @}) if @manual_fetch # TODO: need to write tests and generalize the checks isFetchable
     if model instanceof Backbone.Model
       if @type is 'belongsTo'
-        query.$zero = true unless query.id = model.attributes[@key]?.id
+        (query.$zero = true; delete query.id) unless query.id = model.attributes[@key]?.id
       else
         throw new Error 'Cannot create cursor for non-loaded model' unless model.id
         query[@reverse_relation.foreign_key] = model.id
     else
       # json
       if @type is 'belongsTo'
-        query.$zero = true unless query.id = model[@foreign_key]
+        (query.$zero = true; delete query.id) unless query.id = model[@foreign_key]
       else
         throw new Error 'Cannot create cursor for non-loaded model' unless model.id
         query[@reverse_relation.foreign_key] = model.id


:lipstick: Close resolver dialog on quit event

--- lib/resolver-view.coffee
+++ lib/resolver-view.coffee
@@ -19,6 +19,7 @@
   initialize: (@editor) ->
     @refresh()
     @editor.getBuffer().on 'saved', => @refresh()
+    @subscribe atom, 'merge-conflicts:quit', (event) => @dismiss()

   getModel: -> null

@@ -44,3 +45,6 @@
   resolve: ->
     @editor.save()
     GitBridge.add @relativePath(), => @refresh()
+
+  dismiss: ->
+    @hide 'fast', => @remove()


:bug: Fix writing contents to extra files. Fixes #44

--- lib/sync-settings.coffee
+++ lib/sync-settings.coffee
@@ -110,7 +110,7 @@
           when 'snippets.cson'
             fs.writeFileSync atom.config.configDirPath + "/snippets.coffee", file.content

-          else fs.writeFileSync "#{atom.config.configDirPath}/#{filename}"
+          else fs.writeFileSync "#{atom.config.configDirPath}/#{filename}", file.content

       atom.notifications.addSuccess "sync-settings: Your settings were successfully synchronized."


hideAllPanelsExcept

hideAllPanelsExcept

--- src/panel-container-element.coffee
+++ src/panel-container-element.coffee
@@ -23,9 +23,9 @@
       @insertBefore(panelElement, referenceItem)

     if @model.isModal()
-      @enforceModalityFor(panel)
+      @hideAllPanelsExcept(panel)
       @subscriptions.add panel.onDidChangeVisible (visible) =>
-        @enforceModalityFor(panel) if visible
+        @hideAllPanelsExcept(panel) if visible

   panelRemoved: ({panel, index}) ->
     @removeChild(@childNodes[index])
@@ -34,7 +34,7 @@
     @subscriptions.dispose()
     @parentNode?.removeChild(this)

-  enforceModalityFor: (excludedPanel) ->
+  hideAllPanelsExcept: (excludedPanel) ->
     for panel in @model.getPanels()
       panel.hide() unless panel is excludedPanel
     return


Namespace by tag name.

Namespace by tag name.

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -247,10 +247,11 @@
       secretAccessKey: s3Secret
     s3 = new AWS.S3 s3Info

+    key = "releases/#{release.tag_name}/#{assetName}"
     uploadParams =
       Bucket: s3Bucket
       ACL: 'public-read'
-      Key: "releases/#{assetName}"
+      Key: key
       Body: fs.createReadStream(assetPath)
     s3.upload uploadParams, (error, data) ->
       if error?


Rename fetchCommands to fetchCommand, which is more accurate

--- extension/scripts/background.coffee
+++ extension/scripts/background.coffee
@@ -4,6 +4,7 @@
     "ready.app": "initApp"
     "open.settings": "openSettings"
     "fetch.commands": "fetchCommands"
+    "fetch.commands": "fetchCommand"
     "track": "trackScriptEvent"

   constructor: ->
@@ -48,6 +49,7 @@
     window.Analytics.trackEvent "Open Settings", "Click"

   fetchCommands: (e, command) =>
+  fetchCommand: (e, command) =>
     $.ajax
       url: command.src
       success: (response) =>


remove commented code

remove commented code

--- src/tree.coffee
+++ src/tree.coffee
@@ -123,41 +123,6 @@
   for module in modules then build_tree module
   tree

-# ###
-# Load a module and return its actions
-# A module may be prefixed with:
-# *   "?": Load this module only if it is defined by the user in the run list.
-# *   "!": Force this module to be loaded and executed, apply to "fast" mode.
-# ###
-# Tree::load_module = (module) ->
-#   @cache ?= {}
-#   return @cache[module] if @cache[module]
-#   @cache[module] = actions = []
-#   # Load the module
-#   # required = false
-#   # [_, meta, module] = /([\!\?]?)(.*)/.exec module
-#   # console.log meta, module
-#   # switch meta
-#   #   when '?' then # nothing yet
-#   #   when '!' then required = true
-#   actions = load module
-#   actions = [actions] unless Array.isArray actions
-#   for action, i in actions
-#     # Module dependencies
-#     if typeof action is 'string'
-#       actions.push action
-#       continue
-#     action = callback: action if typeof action is 'function'
-#     action.hidden ?= true unless action.name
-#     action.name ?= "#{module}/#{i}"
-#     action.module ?= module
-#     action.index ?= i
-#     # action.required ?= required
-#     action.skip = false
-#     action.required = true if module.indexOf('phyla/bootstrap') is 0
-#     actions.push action
-#   actions
-
 ###
 Load a module and return its actions.


ApplicationManager: Remove KodingAppsController dependency, no need to fall back to /Develop anymore

--- client/Main/ApplicationManager.coffee
+++ client/Main/ApplicationManager.coffee
@@ -70,7 +70,6 @@
       appParams            = options.params or {}
       appParams.label      = name
       defaultCallback      = createOrShow.bind this, appOptions, appParams, callback
-      kodingAppsController = KD.getSingleton("kodingAppsController")

       # If app has a preCondition then first check condition in it
       # if it returns true then continue, otherwise call failure
@@ -98,11 +97,8 @@
             return warn err  if err
             KD.utils.defer => @open name, options, callback

-        return @fetchManifests name, (err)=>
-          options.avoidRecursion = yes
-          unless err
-          then @open name, options, callback
-          else do defaultCallback
+        options.avoidRecursion = yes
+        return do defaultCallback

       appParams = options.params or {}

@@ -135,26 +131,6 @@
   openFileWithApplication: (appName, file) ->
     @open appName, => @utils.defer => @getFrontApp().openFile file

-  fetchManifests:(appName, callback)->
-
-    KD.getSingleton("kodingAppsController").fetchApps (err, manifests)->
-      manifestsFetched = yes
-
-      return callback? yes  if err or not manifests
-
-      for own name, manifest of manifests when name is appName
-
-        err = no
-
-        manifest.route        = slug : "/Develop/#{encodeURIComponent name}"
-        manifest.behavior   or= "application"
-        manifest.navItem      = title : "Develop"
-        manifest.thirdParty  ?= yes
-
-        KD.registerAppClass KodingAppController, manifest
-
-      callback?()
-
   promptOpenFileWith:(file)->
     finderController = KD.getSingleton "finderController"
     {treeController} = finderController
@@ -203,12 +179,6 @@
     appOptions            = $.extend {}, true, KD.getAppOptions name
     appOptions.params     = params
     @register appInstance = new AppClass appOptions  if AppClass
-
-    if appOptions.thirdParty
-      if KD.getSingleton("kodingAppsController").hasForceUpdate appInstance
-        @emit "AppCouldntBeCreated", appInstance
-        @utils.defer => @quitByName appOptions.name, yes
-        return no

     log 'AppManager: opening an app', name
     if @isAppAvailable name
@@ -403,7 +373,6 @@
       duration  : 2500

   handleAppNotFound: ->
-    KD.getSingleton("router").handleRoute "/Develop"
     new KDNotificationView
       title    : "You don't have this app installed!"
       type     : "mini"


No displayname in cobalt any more

No displayname in cobalt any more

--- client/code/view/admin_content.coffee
+++ client/code/view/admin_content.coffee
@@ -21,7 +21,6 @@
       dataType: 'json'
       data:
         apikey: window.user.real.apiKey
-        displayname: profile.displayName
         email: profile.email[0]
         newApikey: profile.apikey
       success: (cobaltProfile) =>


Comment out a test that doesn't work with PhantomJS

--- client/tests/casper/full/draft.coffee
+++ client/tests/casper/full/draft.coffee
@@ -86,11 +86,10 @@
                     test.assertEquals confirm, "Do you really want to delete message “#{messageSubject}”?", "Confirmation dialog"
                     casper.waitWhileSelector "#email-compose h3[data-message-id=#{messageID}]", ->
                         test.pass 'Compose closed'
-                        casper.waitWhileSelector "li.message[data-message-id='#{messageID}']", ->
-                            test.pass "message deleted"
-                            if casper.getEngine() isnt 'slimer'
-                                test.skip 1
-                            else
+                        if casper.getEngine() is 'slimer'
+                            # delete doesn't work in PhantomJs
+                            casper.waitWhileSelector "li.message[data-message-id='#{messageID}']", ->
+                                test.pass "message deleted"
                                 casper.reload ->
                                     test.assertDoesntExist "li.message[data-message-id='#{messageID}']", "message really deleted"


Oops, fix a typo

Oops, fix a typo

--- backend/sync.coffee
+++ backend/sync.coffee
@@ -44,8 +44,8 @@
             @changes.cancel()
             @changes = null
         async.parallel [
-            (done) => @local.stop callback
-            (done) => @remote.stop callback
+            (done) => @local.stop done
+            (done) => @remote.stop done
         ], callback

     # Start taking changes from pouch and applying them


update directive template to use CDN when specifying a templateUrl

--- directive/templates/directive.coffee
+++ directive/templates/directive.coffee
@@ -17,13 +17,17 @@
 # inject controller dependencies
 <%= controller %>.$inject = ['Config', '$scope', '$element', '$attrs']
 <% } %>
-module.exports = (app)-> app.directive '<%= directive %>', ->
+module.exports = (app)->

-    {
-        restrict: '<%= restrict %>'
-        <% if (transclude) { %>transclude: yes<% } %>
-        <% if (templateUrl) { %>templateUrl: '<%= templateUrl %>'<% } else { %>template: ''<% } %>
-        <% if (controller) { %>controller: <%= controller %>
-        <% } else { %>scope:{ },
-        link: (scope, element, attrs)-><% } %>
-    }+    <% if (templateUrl) { %>CDN = require('CDN')(app)<% } %>
+
+    app.directive '<%= directive %>', ->
+
+        {
+            restrict: '<%= restrict %>'
+            <% if (transclude) { %>transclude: yes<% } %>
+            <% if (templateUrl) { %>templateUrl: CDN.template('<%= templateUrl %>')<% } else { %>template: ''<% } %>
+            <% if (controller) { %>controller: <%= controller %>
+            <% } else { %>scope:{ },
+            link: (scope, element, attrs)-><% } %>
+        }


Fix: Image editor dimensions (? => maximum not minimum)

--- app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js.coffee
+++ app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js.coffee
@@ -323,14 +323,14 @@
               cropper_data['data']['width'] = parseFloat(dim[0].match(/\d+/)[0])
               field_width.val(cropper_data['data']['width'])
               if dim[0].search(/\?/) > -1
-                field_width.attr('min', cropper_data['data']['width'])
+                field_width.attr('max', cropper_data['data']['width'])
               else
                 field_width.prop('readonly', true)
             if dim[1]
               cropper_data['data']['height'] = parseFloat(dim[1].match(/\d+/)[0])
               field_height.val(cropper_data['data']['height'])
               if dim[1].search(/\?/) > -1
-                field_height.attr('min', cropper_data['data']['height'])
+                field_height.attr('max', cropper_data['data']['height'])
               else
                 field_height.prop('readonly', true)
             if dim[0] && dim[0].search(/\?/) == -1 && dim[1] && dim[1].search(/\?/) == -1


Only show OK button on dialog

Only show OK button on dialog

--- src/components/navbar/server-error-monitoring.cjsx
+++ src/components/navbar/server-error-monitoring.cjsx
@@ -1,5 +1,6 @@
 React = require 'react'
 BindStoreMixin = require '../bind-store-mixin'
+BS = require 'react-bootstrap'

 {AppStore} = require '../../flux/app'
 Dialog = require '../tutor-dialog'
@@ -23,7 +24,13 @@

Additional error messages returned from the server is:

{serverErr.message}

-    Dialog.show(title: "An error has occured.  Code: #{serverErr.statusCode}", body: errorMessage)
+    Dialog.show(
+      title: "An error has occured.  Code: #{serverErr.statusCode}", body: errorMessage
+      buttons: [
+        <BS.Button key='ok'
+          onClick={-> Dialog.hide()} bsStyle='primary'>OK</BS.Button>
+      ]
+    )

   # We don't actually render anything


whitespace

whitespace

--- src/wach.coffee
+++ src/wach.coffee
@@ -28,4 +28,4 @@
 #     /Users/bob/someproject/bar.c
 #
 # We parse this into an array of path strings
-parseData = (str) -> str.split('\n')[0...-1]+parseData = (str) -> str.split('\n')[0...-1]


payment: remove unused method from virtualization controller

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -374,22 +374,6 @@
     workflow.on 'Cancel', -> modal.destroy()

     workflow.enter()
-
-  showPaymentMethodForm: (modal) ->
-
-    paymentForm1 = modal.modalTabs.forms['Payment method']
-    paymentForm2 = modal.modalTabs.forms['Payment method 2']
-
-    unless modal.paymentForm
-      modal.paymentForm = new PaymentForm
-
-      paymentController = KD.getSingleton 'paymentController'
-      paymentController.observePaymentSave modal.paymentForm, (err, paymentMethod) ->
-        paymentForm1.addCustomData paymentMethodId: paymentMethod.paymentMethodId
-
-      paymentForm2.addSubView modal.paymentForm
-
-    modal.modalTabs.showNextPane()

   askForApprove:(command, callback)->


Fix invalid session in XAPI connection (should fix #20).

--- src/xapi.coffee
+++ src/xapi.coffee
@@ -32,9 +32,6 @@
     if !force and (hostname is @xmlrpc?.options.host)
       return

-    # Makes sure there is not session id left.
-    delete @sessionId
-
     @xmlrpc = $xmlrpc.createSecureClient {
       host: hostname
       port: port ? 443
@@ -48,13 +45,16 @@
   call: (method, args...) ->
     @connect() unless @xmlrpc

-    args.unshift @sessionId if @sessionId
-
     tries = @tries
     do helper = =>
       try
         result = $wait (callback) =>
-          @xmlrpc.methodCall method, args, callback
+          actualArgs = if @sessionId
+            [@sessionId, args...]
+          else
+            args
+
+          @xmlrpc.methodCall method, actualArgs, callback

         # Returns the plain result if it does not have a valid XAPI format.
         return result unless result.Status?
@@ -85,17 +85,25 @@
           # Node.js seems to reuse the broken socket, so we add a small
           # delay.
           #
-          # TODO Magic number!!!
+          # FIXME Magic number!!!
           #
           # I would like to be able to use a shorter delay but for
           # some reason, when we connect to XAPI at a given moment,
           # the connection hangs.
-          $sleep 500
+          $wait $sleep 5e3
           helper()

         # XAPI is sometimes reinitialized and sessions are lost.
         # We try log in again if necessary.
         when 'SESSION_INVALID'
+          @logIn()
+          helper()
+
+        # Except during the login process, catch this error and try to
+        # log in again.
+        when 'SESSION_AUTHENTICATION_FAILED'
+          throw error unless @sessionId
+
           @logIn()
           helper()

@@ -111,6 +119,9 @@
           throw error

   logIn: ->
+    # Makes sure there is not session id left.
+    delete @sessionId
+
     @sessionId = @call 'session.login_with_password', @username, @password

 #=====================================================================


More testing

More testing

--- test/cli.coffee
+++ test/cli.coffee
@@ -72,6 +72,7 @@

   describe '.create', ->
     Given -> @utils.getGithubUrl = sinon.stub().withArgs('tinder-box', sinon.match.func).callsArgWith 1, null, 'clone url'
+    Given -> @utils.clone = sinon.stub().withArgs(@options, sinon.match.func).callsArgWith 1, null
     Given -> @options = {}
     When -> @subject.create 'horace-the-horrible', 'tinder-box', @options
     Then -> expect(@options.repoName).to.equal 'horace-the-horrible'


remove enc.cofffee

remove enc.cofffee

--- test/env.coffee
+++ test/env.coffee
@@ -1,14 +0,0 @@
-agent = require 'https-proxy-agent'
-
-module.exports =
-	timeout: 400000
-	client:
-		id:		'cP;zbWa@cp4m.iXvSSDr51asIx7as60Sg3?SpcZN'
-		secret: 'TyHvbDwg_D?5aIODJ2;hrH5?pP5TqatIHshope?DD9OGL8Hg4CypT?V_1!Oi1ZQGlnsu?wU80uUmSky;c8fm-qajTXFTh:ewcf7d-COyr6Gt=NwJVMR=I6MDcYxDH2DI'
-	user:
-		id:		'twhtang'
-		secret:	'tom2130'
-	http:
-		opts:
-			timeout: 	10000
-			agent:		new agent('http://proxy1.scig.gov.hk:8080')


add tests

add tests

--- src/mobile/apps/auction_support/test/client/registration_form.coffee
+++ src/mobile/apps/auction_support/test/client/registration_form.coffee
@@ -26,7 +26,7 @@
     benv.teardown()

   beforeEach (done) ->
-    sinon.stub(Backbone, 'sync')#.yieldsTo('success')
+    @submitStub = sinon.stub(Backbone, 'sync')#.yieldsTo('success')

     @order = new Order()
     @sale = new Sale fabricate 'sale'
@@ -51,6 +51,7 @@
   describe '#submit', ->

     beforeEach ->
+      @acceptTerms = => @view.$acceptCOS.prop('checked', true)
       @submitValidForm = =>
         @view.$('input[name="card_name"]').val 'Foo Bar'
         @view.$('select[name="card_expiration_month"]').val '1'
@@ -77,11 +78,12 @@
       @view.on "submitted", =>
         @view.success.called.should.be.ok()
         done()
-
+      @acceptTerms()
       @submitValidForm()

     it 'validates the form and displays errors', (done) ->
       @view.$submit.length.should.be.ok()
+      @acceptTerms()
       @view.$submit.click()

       @view.on "submitted", =>
@@ -116,7 +118,7 @@
         # Successfully create the bidder
         Backbone.sync.onThirdCall().yieldsTo('success')

-
+        @acceptTerms()
         @submitValidForm()
         @view.on "submitted", =>
           @Stripe.card.createToken.args[0][1](200, {})
@@ -141,7 +143,7 @@
       Backbone.sync.onSecondCall().yieldsTo('success')
       # Successfully create the bidder
       Backbone.sync.onThirdCall().yieldsTo('success')
-
+      @acceptTerms()
       @submitValidForm()

       @view.on "submitted", =>
@@ -155,3 +157,12 @@
         # Creates the bidder
         Backbone.sync.args[2][1].attributes.sale_id.should.equal @sale.id
         Backbone.sync.args[2][2].url.should.containEql '/api/v1/bidder'
+
+    it 'does not submit the form if Conditions of Sale are not accepted', ->
+      spy = sinon.spy(@submitStub)
+      @submitValidForm()
+      spy.called.should.be.false()
+
+    it 'adds an error class on submit if Conditions of Sale are not accepted', ->
+      @submitValidForm()
+      @view.$('.artsy-checkbox').hasClass('error').should.be.true()


[grunt] replace `resolveLocaleDir` Nunjucks' function filter with better suiting `localeDir()` global function

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -129,6 +129,17 @@
           # --------------

           ###*
+           * Output or not locale's dir name based on whether it's base locale or not.
+           * Most useful for urls construction
+           * @param {string} locale = currentLocale locale Name of locale, for which should be made resolving
+           * @return {string} Resolved dir name
+           * @example blog link
+          ###
+          env.addGlobal 'localeDir', (locale = currentLocale) ->
+            localeDir = resolveLocaleDir(locale)
+            if localeDir then '/' + localeDir else ''
+
+          ###*
           * Load string from current locale
           * @note So far `sprintf` supports only named placeholders
           * @param {string} string  String, which should be loaded
@@ -250,14 +261,6 @@
           env.addFilter 'pushIn', (array, value) ->
             array.push(value)
             array
-
-          ###*
-           * Output or not locale name based on whether it's base locale or not.
-           * @param {string} locale Name of locale, for which should be made resolving
-           * @return {string} Resolved value
-          ###
-          env.addFilter 'resolveLocaleDir', (locale) ->
-            if resolveLocaleDir(locale) then '/' + resolveLocaleDir(locale) else ''

           ###*
            * Replace placeholders with provided values


Adjust according to feedback on issue #67.

- Rename 'inspector' to 'offline'

- Allow 'offline' to be an array or a function that determines
  if the response should be treated as if the server is offline
  or not. If the server is offline, dualStorage will proceed
  with localsync; otherwise, the error callback will be called.

  An array is expected to contain HTTP status codes. A function
  takes the response object as its argument and return true if
  it should be treated as offline or false otherwise.

- A zero status code is always treated as offline

--- backbone.dualstorage.coffee
+++ backbone.dualstorage.coffee
@@ -244,9 +244,13 @@
   backboneSync(method, model, options)

 bubbleError = (response, options) ->
-  inspector = options.inspector or Backbone.inspector;
-  if inspector then return inspector response
-  return false
+  if response.status and offline = options.offline or Backbone.offline
+    if typeof offline is 'function'
+      return not offline response
+    else
+      return response.status not in offline
+  else
+    return false

 dualsync = (method, model, options) ->
   options.storeName = getStoreName(model.collection, model)


update tests to handle lock internal rename to semaphore

--- test/lib/store/memory.spec.coffee
+++ test/lib/store/memory.spec.coffee
@@ -135,11 +135,11 @@
         store.lockTask(requestor, task.id)

       .then (task) ->
-        expect(task.lock).to.be.an "object"
-        expect(task.lock.time).to.eql now
-        expect(task.lock.key).to.eql expected_key
+        expect(task.semaphore).to.be.an "object"
+        expect(task.semaphore.time).to.eql now
+        expect(task.semaphore.key).to.eql expected_key
         # 30 seconds is the default TTL
-        expect(task.lock.ttl).to.eql 30000
+        expect(task.semaphore.ttl).to.eql 30000

   describe '::unlockTask', ->
     now      = 1433029250000
@@ -197,5 +197,5 @@

       .then (task) ->

-        expect(task.lock).to.eql null
+        expect(task.semaphore).to.eql null
         expect(task.name).to.eql 'test-task'


Fix display of example images

Fix display of example images

--- app/pages/create-project.cjsx
+++ app/pages/create-project.cjsx
@@ -11,6 +11,7 @@
   name: ''
   description: ''
   language: languages[0]
+  example_images: []

 module.exports = React.createClass
   displayName: 'CreateProjectPage'
@@ -44,6 +45,9 @@

Upload some example images

You’ll be able to pick multiple files in the file picker:

         <input type="file" multiple="multiple" name="example_images" onChange={@handleInputChange} />
+

+          {
{file.name}
 for file in newProjectData.example_images}
+

@@ -67,7 +71,7 @@
             Science case

-            { if newProjectData.example_images}
+            { if newProjectData.example_images.length isnt 0}
             Example images

@@ -77,8 +81,14 @@

   handleInputChange: (e) ->
-    console.log 'Changed', e.target
-    changes = new -> @[e.target.name] = e.target.value
+    valueProperty = switch e.target.type
+      when 'radio', 'checkbox' then 'checked'
+      when 'file' then 'files'
+      else 'value'
+
+    changes = {}
+    changes[e.target.name] = e.target[valueProperty]
+
     newProjectData.update changes

   handleSubmit: ->


Make exports button only accessible to admin

--- app/pages/lab/data-dumps.cjsx
+++ app/pages/lab/data-dumps.cjsx
@@ -5,10 +5,12 @@
 counterpart = require 'counterpart'
 DataExportButton = require '../../partials/data-export-button'
 TalkDataExportButton = require '../../talk/data-export-button'
+isAdmin = require '../../lib/is-admin'

 counterpart.registerTranslations 'en',
   projectDetails:
     classificationExport: "Request new classification export"
+    aggregationExport: "Request new aggregation export"
     subjectExport: "Request new subject export"
     workflowExport: "Request new workflow export"
     workflowContentsExport: "Request new workflow contents export"
@@ -56,6 +58,14 @@
               buttonKey="projectDetails.workflowContentsExport"
               exportType="workflow_contents_export"  />

+          {if isAdmin()
+

+              <DataExportButton
+                project={@props.project}
+                buttonKey="projectDetails.aggregationExport"
+                contentType="application/x-gzip"
+                exportType="aggregations_export"  />
+
}

           Talk Data


candidature: add https on config directives

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -20,9 +20,9 @@
       $scope.$watch("url", (value) ->
           if(value)
             if(value.indexOf('http') == -1)
-              $scope.url_image = "http://media.lefresnoy.net/?url=http://api.lefresnoy.net/"+value+"&w="+$scope.width+"h="+$scope.height+"&fmt=jpg"
+              $scope.url_image = "https://media.lefresnoy.net/?url=https://api.lefresnoy.net/"+value+"&w="+$scope.width+"h="+$scope.height+"&fmt=jpg"
             else
-              $scope.url_image = "http://media.lefresnoy.net/?url="+value+"&w="+$scope.width+"h="+$scope.height+"&fmt=jpg"
+              $scope.url_image = "https://media.lefresnoy.net/?url="+value+"&w="+$scope.width+"h="+$scope.height+"&fmt=jpg"

             if $scope.op
               $scope.url_image += "&op=#{ scope.op }"


test2

--- maps/contributors.coffee
+++ maps/contributors.coffee
@@ -45,7 +45,7 @@
     azone_username: ''
     azone_reviews: []
     azone_funds: []
-  }
+  }
   ,
   {
     name: 'Louise Drulhe'


back to enums of strings

back to enums of strings

--- channel_to_JSON_schema.coffee
+++ channel_to_JSON_schema.coffee
@@ -41,17 +41,19 @@
       resource.method = resource.httpMethod.toLocaleUpperCase()

     newChannel.properties.endpoint =
-      type: 'object'
+      type: 'string'
       enum: _.map channel.application.resources, (resource) =>
-        _.pick resource, ['url', 'method']
+        "#{resource.method}-#{resource.url}"

     newChannel.properties.url =
       type: 'string'
       required: true
+      enum: _.uniq _.pluck channel.application.resources, 'url'

     newChannel.properties.method =
       type: 'string'
       required: true
+      enum: _.uniq _.pluck channel.application.resources, 'method'

     form =
       key: 'endpoint'
@@ -67,7 +69,7 @@
       newForm.push
         type: "help"
         helpvalue: "#{resource.method.toLocaleUpperCase()} #{resource.url}"
-        condition: "model.url === '#{resource.url} && model.method === '#{resource.method}''"
+        condition: "model.url === '#{resource.url}' && model.method === '#{resource.method}'"

       _.each resource.params, (param) =>
         newName = "#{resource.method}-#{@sanitizeUrl(resource.url)}##{param.name}"


parse both id and versioned id

parse both id and versioned id

--- src/stores/location.coffee
+++ src/stores/location.coffee
@@ -20,6 +20,7 @@
     Controls: require 'components/vocabulary/controls'
     store:    VocabularyStore
     actions:  VocabularyActions
+

 # The Location class pairs urls with components and stores
 class Location
@@ -50,8 +51,8 @@

   getCurrentUrlParts: ->
     path = window.location.pathname
-    [view, id, args...] = _.tail path.split('/')
-    {view, id, args}
+    [view, versionedId, args...] = _.tail path.split('/')
+    {view, id: versionedId.replace(/@.*/, ''), versionedId, args}

   partsForView: (view = @getCurrentUrlParts().view) ->
     VIEWS[view] or VIEWS['search']


(fix) Coffeescript bracket fuck up.

(fix) Coffeescript bracket fuck up.

--- src/student_queries.coffee
+++ src/student_queries.coffee
@@ -14,10 +14,11 @@
   # Returns all exercises. Expired and active ones.
   getExercises: (result) ->
     # Memory queries don't fail...
-    result null, _(root.DB.Exercises).chain()
-      .filter (ex) -> moment().isAfter ex.activationDate
-      .map (ex) -> (ex.tasks = _.map ex.tasks, (t) -> t.id); ex
-      .value()
+    result null,
+      _(root.DB.Exercises).chain()
+        .filter (ex) -> moment().isAfter ex.activationDate
+        .map (ex) -> (ex.tasks = _.map ex.tasks, (t) -> t.id); ex
+        .value()

   # Returns a specific exercise by id


Team Settings modal can close by overlay clicks

--- client/admin/lib/index.coffee
+++ client/admin/lib/index.coffee
@@ -72,7 +72,6 @@
       width        : 1000
       height       : '90%'
       overlay      : yes
-      overlayClick : no
       tabData      : NAV_ITEMS
     , data


world can find path

world can find path

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -88,19 +88,20 @@
     t.equal result.at(0), "Anjali Prabhakar", "map child"
     t.equal daughter.find_parent("Premela"), grandma, 'Find ancestor'
     t.notOk daughter.find_parent("Abraham"),'Unknown ancestor'
+
     candy = 'chocolate'
-
     t.notOk daughter.get(candy), "No chocolate"
-
     t.notOk daughter.owner(candy), "No chocolate owner"
     grandma.put(candy, 'dark')
     t.equal grandma.owner(candy), grandma, "chocolate owner self"
     t.equal daughter.owner(candy), grandma, "chocolate owner"
     t.equal daughter.get(candy), 'dark', "Inherit chocolate"
-
     mom.put(candy, 0)
     t.equal daughter.owner(candy), mom, "chocolate owner 0"
     t.equal daughter.get(candy), 0, "Inherit chocolate 0"
+    t.end()
+
+  test 'world can find path', (t) ->
     t.end()

   test 'world passes index when mapping', (t) ->


update jquery plugin test

update jquery plugin test

--- test/flexirails/plugin.test.coffee
+++ test/flexirails/plugin.test.coffee
@@ -1,4 +1,4 @@
 module "jquery-flexirails"

-test "true is truthy", ->
-  ok true, "is truthy"+test "$.flexirails should be available", ->
+  equals (typeof $.flexirails), "function", "$.flexirails should be a function"


check extent

check extent

--- src/ubret/sw_viewer.coffee
+++ src/ubret/sw_viewer.coffee
@@ -180,8 +180,8 @@
     scales  = @opts.scales or @defaultScales
     alpha   = @opts.alpha or @defaultAlpha
     Q       = @opts.q or @defaultQ
-    min     = @opts.extent.min or gMin
-    max     = @opts.extent.max or gMax
+    min     = @opts.extent?.min or gMin
+    max     = @opts.extent?.max or gMax

     @wfits.setScales.apply(@wfits, scales)
     @wfits.setAlpha(alpha)
@@ -202,6 +202,7 @@
     })

     band = @opts.band
+    console.log band
     if band?
       if band is 'gri'
         @wfits.drawColor('i', 'r', 'g')


Updated serialize.coffee to AMD

Updated serialize.coffee to AMD

--- src/utils/serialize.coffee
+++ src/utils/serialize.coffee
@@ -1,6 +1,6 @@
-do (window = @, Elyssa = @Elyssa or= {}) ->
+define 'elyssa/serialize', ->

-  Elyssa.serialize = (object, defaultValue) ->
+  serialize = (object, defaultValue) ->
     if defaultValue
       # Remove default values from object to be stringified (we're saving a lot of bytes here)
       for key, value of defaultValue


Disable under construction items from tool menu.

--- src/views/exporting.coffee
+++ src/views/exporting.coffee
@@ -21,14 +21,15 @@
                 @exportingContext.reset()
                 return
             models = [
-                new Backbone.Model
-                    name: i18n.t 'tools.link_action'
-                    action: _.bind @linkAction, @
-                    icon: 'outbound-link'
-                new Backbone.Model
-                    name: i18n.t 'tools.share_action'
-                    action: _.bind @shareAction, @
-                    icon: 'outbound-link'
+                # TODO: implement functionality
+                # new Backbone.Model
+                #     name: i18n.t 'tools.link_action'
+                #     action: _.bind @linkAction, @
+                #     icon: 'outbound-link'
+                # new Backbone.Model
+                #     name: i18n.t 'tools.share_action'
+                #     action: _.bind @shareAction, @
+                #     icon: 'outbound-link'
                 new Backbone.Model
                     name: i18n.t 'tools.embed_action'
                     action: _.bind @embedAction, @


fix globalAlpha as property instead of method

--- src/render/CanvasRender.coffee
+++ src/render/CanvasRender.coffee
@@ -67,7 +67,7 @@
     #
     # @param {float} alpha Alpha value (from .0 to 1.0)
     setAlpha: (alpha) ->
-        @.getContext().globalAlpha alpha
+        @.getContext().globalAlpha = alpha

     # Draw an image on context


""" deleted

""" deleted

I hope it is fixed now.
sorry.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -650,7 +650,7 @@
         "Chardaan Refit":
             name: "Reajuste Chardaan"
             ship: "Ala-A"
-            text: """text: """Solo Ala-A.

This card has a negative squad point cost."""Esta carta tiene un valor negativo en puntos de escuadrón."""
+            text: """text: Solo Ala-A.

This card has a negative squad point cost. Esta carta tiene un valor negativo en puntos de escuadrón."""
         "Proton Rockets":
             name: "Cohetes de Protones"
             text: """Ataque (Concentración): Descarta esta carta para realizar este ataque.

Puedes tirar dados de ataque adicionales igual a tu valor de agilidad, hasta un máximo de 3 dados de ataque adicionales."""


Adding Customer resource to resources service

--- app/assets/javascripts/admin/index_utils/services/resources.js.coffee
+++ app/assets/javascripts/admin/index_utils/services/resources.js.coffee
@@ -1,6 +1,9 @@
 angular.module("admin.indexUtils").factory "resources", ($resource) ->
   LineItem = $resource '/api/orders/:order_number/line_items/:line_item_id.json',
-    { order_number: '@order_cycle_id', line_item_id: '@line_item_id'},
+    { order_number: '@order_number', line_item_id: '@line_item_id'},
+    'update': { method: 'PUT' }
+  Customer = $resource '/admin/customers/:customer_id.json',
+    { customer_id: '@customer_id'},
     'update': { method: 'PUT' }

   return {
@@ -11,11 +14,16 @@

       switch change.type
         when "line_item"
-          resource = LineItem;
+          resource = LineItem
           params.order_number = change.object.order.number
           params.line_item_id = change.object.id
           data.line_item = {}
           data.line_item[change.attr] = change.value
+        when "customer"
+          resource = Customer
+          params.customer_id = change.object.id
+          data.customer = {}
+          data.customer[change.attr] = change.value
         else ""

       resource.update(params, data)


Rerender tasks if controller is executed again.

--- app/assets/javascripts/app/controllers/_application_controller.coffee
+++ app/assets/javascripts/app/controllers/_application_controller.coffee
@@ -587,6 +587,7 @@
     newLocation = newLocation.replace(/#/, '')
     @log 'debug', "execute controller again for '#{newLocation}' because of same hash"
     Spine.Route.matchRoutes(newLocation)
+    @metaTaskUpdate()

   logoUrl: ->
     "#{@Config.get('image_path')}/#{@Config.get('product_logo')}"


update for new bootstrap error class

update for new bootstrap error class

--- app/assets/javascripts/blacklight_cornell_requests/requests.js.coffee
+++ app/assets/javascripts/blacklight_cornell_requests/requests.js.coffee
@@ -83,7 +83,7 @@
   notifyUser: () ->
     requests.scrollToTop()
     $('.flash_messages').html('
-
Please select a new pickup location that does not match the copy location.
+
Please select a new pickup location that does not match the copy location.
        ×

')

@@ -113,7 +113,7 @@
       success: (data) ->
         requests.scrollToTop()
         # Flash validation error if present
-        if data.indexOf('alert-error') != -1
+        if data.indexOf('alert-danger') != -1
           $('.flash_messages').replaceWith(data)

   # Submit purchase form via AJAX


Remove switch statement

Remove switch statement

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -229,11 +229,7 @@

   canSetCurrentBlockAttribute: (attributeName) ->
     block = @getBlock()
-    switch
-      when block.isTerminalBlock()
-        false
-      else
-        true
+    not block.isTerminalBlock()

   setCurrentAttribute: (attributeName, value) ->
     if Trix.config.blockAttributes[attributeName]


Accept optional attributeName argument

Accept optional attributeName argument

--- src/trix/toolbar_controller.coffee
+++ src/trix/toolbar_controller.coffee
@@ -26,7 +26,7 @@

   setAttribute: (dialogElement) ->
     attributeName = getAttributeName(dialogElement)
-    value = getInputForDialog(dialogElement).value
+    value = getInputForDialog(dialogElement, attributeName).value
     @delegate?.toolbarDidUpdateAttribute(attributeName, value)

   removeAttribute: (dialogElement) ->
@@ -51,7 +51,7 @@
     element = @getDialogForAttributeName(attributeName)
     element.classList.add("active")

-    input = element.querySelector("input[name='#{attributeName}']")
+    input = getInputForDialog(element, attributeName)
     input.value = @attributes[attributeName] ? ""
     input.select()

@@ -65,6 +65,6 @@
   getAttributeName = (element) ->
     element.getAttribute("data-attribute")

-  getInputForDialog = (element) ->
-    attributeName = getAttributeName(element)
+  getInputForDialog = (element, attributeName) ->
+    attributeName ?= getAttributeName(element)
     element.querySelector("input[name='#{attributeName}']")


Remove debug log

Remove debug log

--- lib/assets/javascripts/unpoly/classes/layer/overlay.coffee
+++ lib/assets/javascripts/unpoly/classes/layer/overlay.coffee
@@ -141,7 +141,6 @@
             @onOutsideClicked(event, true)

     if @supportsDismissMethod('key')
-      console.log("*** binding escape")
       @unbindEscapePressed = up.event.onEscape((event) => @onEscapePressed(event))

     # OK


activity: add existing channel state variable to new priv. channel modal

--- client/activity/lib/components/createprivatechannelmodal/index.coffee
+++ client/activity/lib/components/createprivatechannelmodal/index.coffee
@@ -32,6 +32,7 @@
       deleteMode          : no
       invalidParticipants : no
       placeholder         : 'type a @username and hit enter'
+      preExistingChannel  : null

   componentWillUnmount: ->


Activity: setting lazy load threshold to 200 pixels

--- client/landing/app/Applications/Activity.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppView.coffee
@@ -47,7 +47,7 @@

     @decorate()
     # after resolving non-blocking socket problem, change this value to 100
-    @setLazyLoader .99
+    @setLazyLoader 200

     {scrollView} = @feedWrapper.controller
     @on "LazyLoadThresholdReached", scrollView.emit.bind scrollView, "LazyLoadThresholdReached"


redirect fix

redirect fix

--- noddy/service/oabutton/redirect.coffee
+++ noddy/service/oabutton/redirect.coffee
@@ -21,7 +21,7 @@
             parts = listing.splash.split(listing.identifier)[0]
             diff = url.replace parts[0], ''
             diff = diff.replace(parts[1]) if parts.length > 1
-            url = listing.fulltext.replace litsing.identifier, diff
+            url = listing.fulltext.replace listing.identifier, diff
           else if listing.element and url.indexOf('.pdf') is -1
             try
               content = API.http.phantom url


Hero: adding scale effect a bit later to try to avoid blur. fixed stagger on outro when only 4 shapes

--- app/_src/components/hero-noise/hero-noise.coffee
+++ app/_src/components/hero-noise/hero-noise.coffee
@@ -1,13 +1,12 @@
 class FOURTEEN.HeroNoise extends FOURTEEN.BaseComponent

 	env = {
-	  autoAnimate: true
-	  speed: 0.6
-	  spreadX: -1
-	  spreadY: -1
-	  rotation: Math.PI/20
-	  scaleEffect: 0
-	  parallaxEffect: 0.01
+		spreadX:  window.innerWidth * 0.5
+		spreadY: window.innerHeight * 0.66
+		speed: 0.02
+		rotation: Math.PI*5
+		scaleEffect: 0 # start with no scale to avoid blur
+		parallaxEffect: 0.2
 	}

 	mouseCurrent = {mouseX: window.innerWidth/2, mouseY: window.innerHeight/2}
@@ -40,13 +39,21 @@
 		@timeline.pause()
 		@animateOut(=>
 			@isAnimating = false
+			# set back to initial env (copy somehow?)
+			env.spreadX = window.innerWidth * 0.5
+			env.spreadY = window.innerHeight * 0.66
+			env.speed = 0.02
+			env.rotation = Math.PI*5
+			env.scaleEffect = 0
+			env.parallaxEffect = 0.2
 		)

 	resumeAnimation: =>
 		@isAnimating = true
 		@renderLoop()
-		@startTimeline()
-		@animateIn()
+		@animateIn(=>
+			@startTimeline()
+		)

 	onLogoClick: =>
@@ -55,7 +62,7 @@
 			if time is 0
 				@timeline.restart()
 			else
-				@timeline.timeScale(2)
+				@timeline.timeScale(2.5)
 				@timeline.reverse()

 	# keep track of the mouse position
@@ -80,27 +87,32 @@

 		if @isAnimating then window.requestAnimationFrame(@renderLoop)

-	getIntroSpread: =>
-		if (window.innerHeight > window.innerWidth)
-			return window.innerHeight / 2
-		else
-			return window.innerWidth / 10

 	animateIn: (complete) =>
+		# add scale effect with delay so they render at full size to avoid blur
+		TweenMax.to(env, 1.0, {
+			scaleEffect: 1,
+			ease: Power2.easeInOut,
+			delay: 0.15,
+			onComplete: complete
+		})
 		# fade in
+		staggerDelay = 0.7 / 14
 		TweenMax.staggerTo(@paths, .8, {
 			opacity: 1,
 			ease: Power2.easeInOut,
-			onComplete: complete
-		}, 0.05)
+			# onComplete: complete
+		}, staggerDelay)

 	animateOut: (complete) =>
-		# move back to outer edge
+		# spin out
+		numVisible = @$logoEl.children(':visible').length
+		staggerDelay = 0.7 / numVisible
 		TweenMax.staggerTo(@paths, 0.2, {
 			opacity: 0,
 			delay: 0.2
 			ease: Power4.easeInOut
-		}, 0.05)
+		}, staggerDelay)
 		TweenMax.killTweensOf(env)
 		TweenMax.to(env, 1.2, {
 		  speed: 0.2
@@ -122,7 +134,7 @@
 	# loop between exploded and logo state
 	startTimeline: =>
 		TweenMax.killTweensOf(env)
-		@timeline = new TimelineMax({paused: false, delay: 3})
+		@timeline = new TimelineMax({paused: false, delay: 2})

 		@timeline.fromTo(env, 2, {
 			spreadX:  window.innerWidth * 0.5


Mousedown event for transactions

Mousedown event for transactions

--- app/assets/javascripts/app/transaction.js.coffee
+++ app/assets/javascripts/app/transaction.js.coffee
@@ -116,7 +116,7 @@
       )

     # To add new items
-    $('tr.item a.ajax').live 'mouseover', (event)->
+    $('tr.item a.ajax').live 'mousedown', (event)->
       self.currentRow = $(this).parents("tr.item")

     $('body').live 'add:item', (event, vals)=>


Set custom styles for CKEditor

Set custom styles for CKEditor

--- app/assets/javascripts/ckeditor/config.js.coffee
+++ app/assets/javascripts/ckeditor/config.js.coffee
@@ -23,6 +23,10 @@
     config.toolbar = 'Pure'
     config.toolbarCanCollapse = true
     config.toolbarStartupExpanded = false
+
+    config.stylesSet = [
+      { name: 'Ladillo', element: 'h2', styles: { } }
+    ]
     true

 if CKEDITOR?


fixing a bug where isMobile was always true

--- app/assets/javascripts/views/base_view.js.coffee
+++ app/assets/javascripts/views/base_view.js.coffee
@@ -45,5 +45,5 @@
 		view.parentView = @
 		view

-	#isMobile: -> jQuery.browser.mobile
-	isMobile: -> yes+	isMobile: -> jQuery.browser.mobile
+	#isMobile: -> yes


added unit tests

added unit tests

--- test/unit/wd-by-method-test.coffee
+++ test/unit/wd-by-method-test.coffee
@@ -119,6 +119,14 @@
   it "element", WdWrap ->
     should.exist @element "name", "elementByName"
     (-> @element "name", "elementByName2").should.throw()
+
+  it "elementOrNull", WdWrap ->
+    should.exist @elementOrNull "name", "elementByName"
+    should.not.exist @elementOrNull "name", "elementByName2"
+
+  it "elementIfExists", WdWrap ->
+    should.exist @elementIfExists "name", "elementByName"
+    should.not.exist @elementIfExists "name", "elementByName2"

   it "hasElement", WdWrap ->
     (@hasElement "name", "elementByName").should.be.true;


fix DB creation : (see https://github.com/mycozycloud/cozy-data-system/issues/1)

--- config/initializers/db.coffee
+++ config/initializers/db.coffee
@@ -13,7 +13,9 @@
         else if exists
             console.log "Database Cozy found."
         else
-            console.log "database does not exists."
-            db.create()
+            console.log "Database Cozy does not exist."
+            db.create ->
+                console.log "Database Cozy created."
+                return
+
 createDb()
-