:boar: Update uglify task

:boar: Update uglify task

--- grunt/uglify.coffee
+++ grunt/uglify.coffee
@@ -1,12 +1,12 @@
-module.exports =
-  options:
-    report: 'gzip'
-  compact:
-    files: [{
-      expand: true
-      cwd: 'dist/'
-      src: ['**/*.js']
-      dest: 'dist/'
-      ext: '.min.js'
-      extDot: 'last'
-    }]+module.exports =
+  options:
+    report: 'gzip'
+  compact:
+    files: [{
+      expand: true
+      cwd: 'dist/amd'
+      src: ['**/*.js']
+      dest: 'dist/amd'
+      ext: '.min.js'
+      extDot: 'last'
+    }]


The name argument isn't being used here.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -63,7 +63,7 @@
 handleCompletions = (sender) -> (request, port) ->
   completionHandlers[request.handler] completers[request.name], request, port

-chrome.runtime.onConnect.addListener (port, name) ->
+chrome.runtime.onConnect.addListener (port) ->
   if (portHandlers[port.name])
     port.onMessage.addListener portHandlers[port.name] port.sender, port


channel

channel

--- routes/lib/speaker.coffee
+++ routes/lib/speaker.coffee
@@ -7,7 +7,10 @@

 module.exports = (message) ->
   console.log message
-  channel = this.body.channel_name;
+  if this.body?
+    channel = this.body.channel_name;
+  else
+    channel = "announcement"
   option =
     channel: "#" + channel,
     username: "PeoplyBot",


Handle errors from applications kite and show a proper modal for them

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -224,15 +224,10 @@
     if KDApps[name]
       callback null, KDApps[name]
     else
-
       @fetchCompiledApp manifest, (err, script)=>
         if err
-          @compileApp name, (err)=>
-            if err
-              new KDNotificationView type : "mini", title : "There was an error, please try again later!"
-              callback err
-            else
-              callback err, KDApps[name]
+          @compileApp name, (err)->
+            callback err, KDApps[name]
         else
           @defineApp name, script
           callback err, KDApps[name]
@@ -392,19 +387,47 @@
     compileOnServer = (app)=>
       return warn "#{name}: No such application!" unless app
       appPath = getAppPath app
+
+      loader = new KDNotificationView
+        duration : 18000
+        title    : "Compiling #{name}..."
+        type     : "mini"
+
       @kiteController.run
         kiteName  : "applications"
         method    : "compileApp"
-        withArgs  :
-          appPath : appPath
+        withArgs  : {appPath}
       , (err)=>
         if not err
+          loader.notificationSetTitle "Fetching compiled app..."
           @fetchCompiledApp app, (err, res)=>
-            if not err then @defineApp name, res
-            callback?()
-        else
-          warn "An error occured while compiling:", err
-          callback? yes
+            if not err
+              @defineApp name, res
+              loader.notificationSetTitle "App compiled successfully"
+              loader.notificationSetTimer 2000
+            callback? err
+        else
+          loader.destroy()
+
+          if err.details?.details?
+            details = """
ERROR: #{err.details.details}

+                              FILE : #{err.details.file}

"""
+          else if err.details?
+            details = "
#{err.details}
"
+          else
+            details = ""
+
+          new KDModalView
+            title   : "An error occured while compiling the App!"
+            width   : 500
+            overlay : yes
+            content : """
+

+
#{err.message}

+                        #{details}
+

+                      """
+          callback? err

     unless @constructor.manifests[name]
       @fetchApps (err, apps)->


Check if view exists before trying to get its index

--- client/app/MainApp/maintabs/maintabview.coffee
+++ client/app/MainApp/maintabs/maintabview.coffee
@@ -89,7 +89,7 @@
     @_removePane pane

   getPaneByView:(view)->
-    @paneViewIndex[view.id]
+    if view then @paneViewIndex[view.id] else null

   indexPaneByView:(pane,view)->
     @paneViewIndex[view.id] = pane


sidebar: load visibility filters before anything

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -13,6 +13,7 @@
 SharingMachineInvitationWidget = require 'app/components/sidebarmachineslistitem/sharingmachineinvitationwidget'
 SidebarDifferentStackResources = require 'app/components/sidebarstacksection/sidebardifferentstackresources'
 { findDOMNode } = require 'react-dom'
+SidebarFlux = require 'app/flux/sidebar'

 MENU = null

@@ -44,11 +45,12 @@

   componentWillMount: ->

-    EnvironmentFlux.actions.loadStacks()
-    EnvironmentFlux.actions.loadMachines().then @bound 'setActiveInvitationMachineId'
-
-    EnvironmentFlux.actions.loadTeamStackTemplates()
-    EnvironmentFlux.actions.loadPrivateStackTemplates()
+    SidebarFlux.actions.loadVisibilityFilters().then =>
+      EnvironmentFlux.actions.loadStacks()
+      EnvironmentFlux.actions.loadMachines().then @bound 'setActiveInvitationMachineId'
+
+      EnvironmentFlux.actions.loadTeamStackTemplates()
+      EnvironmentFlux.actions.loadPrivateStackTemplates()

     # These listeners needs to be listen those events only once ~ GG
     kd.singletons.notificationController


sidebar: removed messages and channels

sidebar: removed messages and channels

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -176,8 +176,6 @@
         {@renderStacks()}
         {@renderNoStacks()}
         {@renderSharedMachines()}
-        {@renderChannels()}
-        {@renderMessages()}
         {@renderInvitationWidget()}
       </div>
       <div className='Sidebar-logo-wrapper'>


compute/connect: don't try to bind events when not needed

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/providers/connectcompute.coffee
+++ client/app/lib/providers/connectcompute.coffee
@@ -58,9 +58,12 @@
       @_mounted = yes

       { computeController } = kd.singletons
+
+      computeController.storage.on 'change', @bound 'onStorageUpdate'
+
       { controllerEvents } = config

-      computeController.storage.on 'change', @bound 'onStorageUpdate'
+      return  unless controllerEvents

       Object.keys(controllerEvents).forEach (resource) =>
         return  unless resourceId = @props["#{resource}Id"]


added commented-out replybox fix that doesn't actually work.

--- client/coffeescript/templates/reply_box.coffee
+++ client/coffeescript/templates/reply_box.coffee
@@ -13,6 +13,13 @@
       if not event.isImmediatePropagationStopped()
         Session.set("composing_#{ @_id }", "")
         event.stopImmediatePropagation()
+
+    ###
+    "load textarea[name='reply_text']": (event) ->
+      if not event.isImmediatePropagationStopped()
+        event.target.focus()
+        event.stopImmediatePropagation()
+    ###

     #editing
     """


近いものは近づける

近いものは近づける

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -4,16 +4,16 @@

 SocketService = require('./SocketService')

+myGuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
+  r = Math.random()*16|0
+  v = if c == 'x' then r else (r&0x3|0x8)
+  v.toString(16)
+
 webSocketUrl =
   if (location.hash == '#dev')
     'localhost:8001/websocket'
   else
     'erl-editor.herokuapp.com/websocket'
-myGuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
-  r = Math.random()*16|0
-  v = if c == 'x' then r else (r&0x3|0x8)
-  v.toString(16)
-
 socketService = new SocketService(webSocketUrl)

 defaulMarkdown = '''


kite/newkite.coffee: re-request new token if previous one is invoked

--- client/landing/app/MainApp/kite/newkite.coffee
+++ client/landing/app/MainApp/kite/newkite.coffee
@@ -75,12 +75,15 @@
   onMessage: (evt) ->
     try
       args = JSON.parse evt.data
+      err = args.arguments[0]
+      if err?.message?
+          KD.utils.defer => @setBackoffTimeout @bound "getKiteAddr"
+
       {method} = args
       callback = switch method
         when 'ping'             then @bound 'handlePing'
         else (@localStore.get method) ? ->

-      # callback = @localStore.get method
       callback.apply this, @unscrub args
     catch e
       console.log "error: ", e, evt.data


state.coffeeをコントローラーとして活用検討したがこれは不要

state.coffeeをコントローラーとして活用検討したがこれは不要

--- coffee/state.coffee
+++ coffee/state.coffee
@@ -1,6 +0,0 @@
-class state
-  constructor: () ->
-    @state = new defaultState()
-
-module.exports = state
-


mixin custom props and use only them

mixin custom props and use only them

--- client/lanes/components/grid/PopOverMixin.cjsx
+++ client/lanes/components/grid/PopOverMixin.cjsx
@@ -13,23 +13,23 @@
     getDefaultProps: ->
         width:  280, height: 350

-    renderPopover: (child) ->
-        props = _.extend({}, @props)
-        {position} = @props
+    renderPopover: (props) ->
+        props = _.extend({}, @props, props)
+        {position} = props
         if position.left > ((position.container.width / 2) - 150)
             props.placement = 'left'
-            props.positionLeft = position.left - @props.width
+            props.positionLeft = position.left - props.width
         else
             props.placement = 'right'
             props.positionLeft = position.left
-        props.arrowOffsetTop = Math.min(position.top + 20, (@props.height - 75))
+        props.arrowOffsetTop = Math.min(position.top + 20, (props.height - 75))
         props.positionTop    = Math.max(5, position.top - props.arrowOffsetTop + (position.rowHeight / 2))

         <div className="editor po">
             <BS.Popover
                 id="editing-form"
                 {...props}
-                style={height: @props.height, width: @props.width}
+                style={height: props.height, width: props.width}
                 title = "Edit #{@model.name || 'Record'}"
             >
                 {@renderEditingBody()}


Nicer-looking --tokenize, more in line with what Ruby's doing

--- command_line.coffee
+++ command_line.coffee
@@ -63,7 +63,7 @@
   return unless source: @sources.shift()
   opts: @options
   posix.cat(source).addCallback (code) ->
-    if      opts.tokens   then puts coffee.tokenize(code).join(' ')
+    if      opts.tokens   then puts exports.tokenize(code)
     else if opts.tree     then puts coffee.tree(code).toString()
     else
       js: coffee.compile code
@@ -80,6 +80,12 @@
   js_path:  path.join dir, filename
   posix.open(js_path, process.O_CREAT | process.O_WRONLY | process.O_TRUNC, parseInt('0755', 8)).addCallback (fd) ->
     posix.write(fd, js)
+
+# Pretty-print the token stream.
+exports.tokenize: (code) ->
+  strings: coffee.tokenize(code).map (token) ->
+    '[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']'
+  strings.join(' ')

 # Pipe compiled JS through JSLint (requires a working 'jsl' command).
 exports.lint: (js) ->


Add test for destroy

Add test for destroy

--- test/src/backbone.websql.deferred-test.coffee
+++ test/src/backbone.websql.deferred-test.coffee
@@ -103,3 +103,16 @@
         expect(rows.length).to.eq 1
         expect(rows[0]['name']).to.eq 'user2'
         done()
+
+  describe '.destroy', ->
+    beforeEach (done) =>
+      @user = new User name: 'bob'
+      @user.save().done do (done) =>
+        done()
+
+    it 'should be destroyed', (done) =>
+      @user.destroy().done do (done) =>
+        users = new Users id: @user.get 'id'
+        users.fetch().done (rows) =>
+          expect(rows.length).to.eq 0
+          done()


Added some extra resource tests (now almost all service tested)

--- test/unit/services/resourceServiceSpec.coffee
+++ test/unit/services/resourceServiceSpec.coffee
@@ -135,6 +135,83 @@
         httpBackend.whenPOST('http://localhost:8000/api/v1/userstories/bulk_create', {"test": "bad", "projectId": 1}).respond(400)
         httpBackend.whenPOST('http://localhost:8000/api/v1/tasks/bulk_create', {"test": "test", "projectId": 1, "usId": 2}).respond(200)
         httpBackend.whenPOST('http://localhost:8000/api/v1/tasks/bulk_create', {"test": "bad", "projectId": 1, "usId": 2}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/userstories/bulk_update_order', {"projectId": 1, "bulkStories": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/userstories/bulk_update_order', {"projectId": 100, "bulkStories": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenPATCH('http://localhost:8000/api/v1/userstories/1', {"milestone": 2}).respond(200)
+        httpBackend.whenPATCH('http://localhost:8000/api/v1/userstories/100', {"milestone": 1}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/milestones', {"project": 1, "test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/milestones', {"project": 100, "test": "test"}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki?project=1&slug=test').respond(200, "wiki")
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki?project=1&slug=bad').respond(400, "wiki")
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki?project=1&slug=empty').respond(200, [])
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki/test/historical').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki/test/historical?filters=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki/bad/historical?filters=test').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/tasks?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/tasks?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/wiki/test/restore?version=1').respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/wiki/bad/restore?version=1').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/wiki', {"content": "test", "slug": "test-slug", "project": 1}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/wiki', {"content": "bad", "slug": "test-slug", "project": 1}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-attachments?object_id=1&project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-attachments?object_id=1&project=100').respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/task-attachments?object_id=1&project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/task-attachments?object_id=1&project=100').respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/userstory-attachments?object_id=1&project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/userstory-attachments?object_id=1&project=100').respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki-attachments?object_id=1&project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/wiki-attachments?object_id=1&project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-attachments', {"project": 1, "object_id": 1, "data": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-attachments', {"project": 1, "object_id": 1, "data": "bad"}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/userstory-statuses?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/userstory-statuses?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/userstory-statuses?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/userstory-statuses?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/userstory-statuses?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/userstory-statuses/bulk_update_order', {"project": 1, "bulk_userstory_statuses": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/userstory-statuses/bulk_update_order', {"project": 100, "bulk_userstory_statuses": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/points?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/points?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/points?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/points?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/points?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/points/bulk_update_order', {"project": 1, "bulk_points": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/points/bulk_update_order', {"project": 100, "bulk_points": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/task-statuses?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/task-statuses?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/task-statuses?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/task-statuses?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/task-statuses?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/task-statuses/bulk_update_order', {"project": 1, "bulk_task_statuses": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/task-statuses/bulk_update_order', {"project": 100, "bulk_task_statuses": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-statuses?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-statuses?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-statuses?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-statuses?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-statuses?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-statuses/bulk_update_order', {"project": 1, "bulk_issue_statuses": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-statuses/bulk_update_order', {"project": 100, "bulk_issue_statuses": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-types?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-types?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/issue-types?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-types?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-types?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-types/bulk_update_order', {"project": 1, "bulk_issue_types": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/issue-types/bulk_update_order', {"project": 100, "bulk_issue_types": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/priorities?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/priorities?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/priorities?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/priorities?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/priorities?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/priorities/bulk_update_order', {"project": 1, "bulk_priorities": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/priorities/bulk_update_order', {"project": 100, "bulk_priorities": [[1, 2], [2, 1]]}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/severities?project=1').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/severities?project=1&test=test').respond(200)
+        httpBackend.whenGET('http://localhost:8000/api/v1/severities?project=100').respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/severities?', {"test": "test"}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/severities?', {"test": "bad"}).respond(400)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/severities/bulk_update_order', {"project": 1, "bulk_severities": [[1, 2], [2, 1]]}).respond(200)
+        httpBackend.whenPOST('http://localhost:8000/api/v1/severities/bulk_update_order', {"project": 100, "bulk_severities": [[1, 2], [2, 1]]}).respond(400)

     describe 'resource service', ->
         afterEach ->
@@ -612,3 +689,429 @@
             promise = resource.createBulkTasks(1, 2, {"test": "bad"})
             promise.should.be.rejected
             httpBackend.flush()
+
+        it 'should allow to create a bulk of tasks', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/tasks/bulk_create', {"test": "test", "projectId": 1, "usId": 2})
+            promise = resource.createBulkTasks(1, 2, {"test": "test"})
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/tasks/bulk_create', {"test": "bad", "projectId": 1, "usId": 2})
+            promise = resource.createBulkTasks(1, 2, {"test": "bad"})
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow update the user stories order in bulk', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/userstories/bulk_update_order', {"projectId": 1, "bulkStories": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkUserStoriesOrder(1, [[1, 2], [2, 1]])
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/userstories/bulk_update_order', {"projectId": 100, "bulkStories": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkUserStoriesOrder(100, [[1, 2], [2, 1]])
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow set the milestone of a user story', inject (resource) ->
+            httpBackend.expectPATCH('http://localhost:8000/api/v1/userstories/1', {"milestone": 2})
+            promise = resource.setUsMilestone(1, 2)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPATCH('http://localhost:8000/api/v1/userstories/100', {"milestone": 1})
+            promise = resource.setUsMilestone(100, 1)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow create a milestone', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/milestones', {"project": 1, "test": "test"})
+            promise = resource.createMilestone(1, {"test": "test"})
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/milestones', {"project": 100, "test": "test"})
+            promise = resource.createMilestone(100, {"test": "test"})
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get a wiki page', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki?project=1&slug=test')
+            promise = resource.getWikiPage(1, "test")
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki?project=1&slug=bad')
+            promise = resource.getWikiPage(1, "bad")
+            promise.should.be.rejected
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki?project=1&slug=empty')
+            promise = resource.getWikiPage(1, "empty")
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get a wiki page historical', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki/test/historical')
+            promise = resource.getWikiPageHistorical("test")
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki/test/historical?filters=test')
+            promise = resource.getWikiPageHistorical("test", {"filters": "test"})
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki/bad/historical?filters=test')
+            promise = resource.getWikiPageHistorical("bad", {"filters": "test"})
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a task', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/tasks?', {"test": "test"})
+            promise = resource.createTask({"test": "test"})
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/tasks?', {"test": "bad"})
+            promise = resource.createTask({"test": "bad"})
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to restore a wiki page', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/wiki/test/restore?version=1')
+            promise = resource.restoreWikiPage("test", 1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/wiki/bad/restore?version=1')
+            promise = resource.restoreWikiPage("bad", 1)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a wiki page', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/wiki', {"content": "test", "slug": "test-slug", "project": 1})
+            promise = resource.createWikiPage(1, "test-slug", "test")
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/wiki', {"content": "bad", "slug": "test-slug", "project": 1})
+            promise = resource.createWikiPage(1, "test-slug", "bad")
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the attachments of an issue', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-attachments?object_id=1&project=1')
+            promise = resource.getIssueAttachments(1, 1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-attachments?object_id=1&project=100')
+            promise = resource.getIssueAttachments(100, 1)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the attachments of a task', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/task-attachments?object_id=1&project=1')
+            promise = resource.getTaskAttachments(1, 1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/task-attachments?object_id=1&project=100')
+            promise = resource.getTaskAttachments(100, 1)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the attachments of an issue', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/userstory-attachments?object_id=1&project=1')
+            promise = resource.getUserStoryAttachments(1, 1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/userstory-attachments?object_id=1&project=100')
+            promise = resource.getUserStoryAttachments(100, 1)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the attachments of an issue', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki-attachments?object_id=1&project=1')
+
+            promise = resource.getWikiPageAttachments(1, 1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/wiki-attachments?object_id=1&project=100')
+            promise = resource.getWikiPageAttachments(100, 1)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the site info', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/sites')
+            promise = resource.getSiteInfo()
+            promise.should.fullfilled
+            httpBackend.flush()
+
+        it 'should allow to get the user stories statuses', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/userstory-statuses?project=1')
+            promise = resource.getUserStoryStatuses(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/userstory-statuses?project=1&test=test')
+            promise = resource.getUserStoryStatuses(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/userstory-statuses?project=100')
+            promise = resource.getUserStoryStatuses(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a user stories status', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/userstory-statuses?', {"test": "test"})
+            promise = resource.createUserStoryStatus({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/userstory-statuses?', {"test": "bad"})
+            promise = resource.createUserStoryStatus({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the user stories statuses orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/userstory-statuses/bulk_update_order', {"project": 1, "bulk_userstory_statuses": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkUserStoryStatusesOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/userstory-statuses/bulk_update_order', {"project": 100, "bulk_userstory_statuses": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkUserStoryStatusesOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the user stories points', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/points?project=1')
+            promise = resource.getPoints(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/points?project=1&test=test')
+            promise = resource.getPoints(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/points?project=100')
+            promise = resource.getPoints(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a user stories status', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/points?', {"test": "test"})
+            promise = resource.createPoints({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/points?', {"test": "bad"})
+            promise = resource.createPoints({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the user stories points orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/points/bulk_update_order', {"project": 1, "bulk_points": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkPointsOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/points/bulk_update_order', {"project": 100, "bulk_points": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkPointsOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the tasks statuses', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/task-statuses?project=1')
+            promise = resource.getTaskStatuses(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/task-statuses?project=1&test=test')
+            promise = resource.getTaskStatuses(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/task-statuses?project=100')
+            promise = resource.getTaskStatuses(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a tasks status', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/task-statuses?', {"test": "test"})
+            promise = resource.createTaskStatus({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/task-statuses?', {"test": "bad"})
+            promise = resource.createTaskStatus({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the tasks statuses orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/task-statuses/bulk_update_order', {"project": 1, "bulk_task_statuses": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkTaskStatusesOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/task-statuses/bulk_update_order', {"project": 100, "bulk_task_statuses": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkTaskStatusesOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the issues statuses', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-statuses?project=1')
+            promise = resource.getIssueStatuses(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-statuses?project=1&test=test')
+            promise = resource.getIssueStatuses(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-statuses?project=100')
+            promise = resource.getIssueStatuses(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a issues status', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-statuses?', {"test": "test"})
+            promise = resource.createIssueStatus({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-statuses?', {"test": "bad"})
+            promise = resource.createIssueStatus({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the issues statuses orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-statuses/bulk_update_order', {"project": 1, "bulk_issue_statuses": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkIssueStatusesOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-statuses/bulk_update_order', {"project": 100, "bulk_issue_statuses": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkIssueStatusesOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the issues types', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-types?project=1')
+            promise = resource.getIssueTypes(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-types?project=1&test=test')
+            promise = resource.getIssueTypes(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/issue-types?project=100')
+            promise = resource.getIssueTypes(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a issues type', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-types?', {"test": "test"})
+            promise = resource.createIssueType({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-types?', {"test": "bad"})
+            promise = resource.createIssueType({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the issues types orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-types/bulk_update_order', {"project": 1, "bulk_issue_types": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkIssueTypesOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/issue-types/bulk_update_order', {"project": 100, "bulk_issue_types": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkIssueTypesOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the priorities', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/priorities?project=1')
+            promise = resource.getPriorities(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/priorities?project=1&test=test')
+            promise = resource.getPriorities(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/priorities?project=100')
+            promise = resource.getPriorities(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a priority', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/priorities?', {"test": "test"})
+            promise = resource.createPriority({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/priorities?', {"test": "bad"})
+            promise = resource.createPriority({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the priorities orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/priorities/bulk_update_order', {"project": 1, "bulk_priorities": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkPrioritiesOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/priorities/bulk_update_order', {"project": 100, "bulk_priorities": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkPrioritiesOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to get the severities', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/severities?project=1')
+            promise = resource.getSeverities(1)
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/severities?project=1&test=test')
+            promise = resource.getSeverities(1, {"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/severities?project=100')
+            promise = resource.getSeverities(100)
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to create a severity', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/severities?', {"test": "test"})
+            promise = resource.createSeverity({"test": "test"})
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/severities?', {"test": "bad"})
+            promise = resource.createSeverity({"test": "bad"})
+            promise.should.rejected
+            httpBackend.flush()
+
+        it 'should allow to bulk update the severities orders', inject (resource) ->
+            httpBackend.expectPOST('http://localhost:8000/api/v1/severities/bulk_update_order', {"project": 1, "bulk_severities": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkSeveritiesOrder(1, [[1, 2], [2, 1]])
+            promise.should.fullfilled
+            httpBackend.flush()
+
+            httpBackend.expectPOST('http://localhost:8000/api/v1/severities/bulk_update_order', {"project": 100, "bulk_severities": [[1, 2], [2, 1]]})
+            promise = resource.updateBulkSeveritiesOrder(100, [[1, 2], [2, 1]])
+            promise.should.rejected
+            httpBackend.flush()


Improve UX, tap toggles InStateMenu by default.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -52,7 +52,10 @@
       @quitKey = @_addKey Keyboard.Q, @quit
       # Quit on click at end.
       @onHit = @input[if @game.device.touch then 'onTap' else 'onUp']
-      @onHit.add @quit, @
+      @onHit.add (trigger) =>
+        unless @quit trigger
+          @inStateMenu.toggle()
+      , @

       if @developing
         @gui = new dat.GUI()


KDContentEditableView: do not focus if already focused

--- components/inputs/KDContentEditableView.coffee
+++ components/inputs/KDContentEditableView.coffee
@@ -79,7 +79,7 @@
     else
       @setContent @getValue() if @getOptions().type isnt "html"

-  click: => @focus() if @editingMode
+  click: => @focus() if @editingMode and not @focused

   input: (event) =>
     @emit "ValueChanged", event


Removed FactsNewView entirely

Removed FactsNewView entirely

--- core/app/backbone/client/facts_new_view.coffee
+++ core/app/backbone/client/facts_new_view.coffee
@@ -1,45 +0,0 @@
-  post_factlink: (e)->
-    @ui.post_factlink.prop('disabled', true).text('Posting...')
-
-    channel_ids = @addToCollection.pluck('id')
-
-    fact = new Fact
-      opinion: @wheel.get('current_user_opinion')
-      displaystring: @options.fact_text
-      fact_url: @options.url
-      fact_title: @options.title
-      channels: channel_ids
-
-    fact.save {},
-      success: =>
-        fact.set containing_channel_ids: channel_ids
-        @trigger 'factCreated', fact
-
-  openOpinionHelptext: ->
-    if FactlinkApp.guided
-      @popoverAdd '.fact-wheel',
-        side: 'left'
-        align: 'top'
-        margin: 20
-        popover_className: 'fact-new-opinion-popover factlink-popover'
-        contentView: new Backbone.Marionette.ItemView(template: 'tour/give_your_opinion')
-    else
-      @popoverAdd '.fact-wheel',
-        side: 'top'
-        popover_className: 'translucent-popover'
-        contentView: new TextView text: "What's your opinion?"
-
-  closeOpinionHelptext: ->
-    @popoverRemove('.fact-wheel')
-
-    if FactlinkApp.guided
-      @openFinishHelptext()
-
-  openFinishHelptext: ->
-    unless @popoverOpened(".js-submit-post-factlink")?
-      @popoverAdd '.js-submit-post-factlink',
-        side: 'right'
-        align: 'top'
-        margin: 19
-        container: @$('.js-finish-popover')
-        contentView: new Backbone.Marionette.ItemView(template: 'tour/post_factlink')


:lipstick: Remove extra newline

:lipstick: Remove extra newline

--- src/text-utils.coffee
+++ src/text-utils.coffee
@@ -32,7 +32,6 @@
 # * `index`  The {Number} index to look for a surrogate pair at.
 #
 # Return a {Boolean}.
-
 isPairedCharacter = (string, index=0) ->
   isSurrogatePair(string, index) or isVariationSequence(string, index)


Shorten mention of MIT license

Shorten mention of MIT license

--- src/touchclick.coffee
+++ src/touchclick.coffee
@@ -1,8 +1,5 @@
 ###!
-Copyright (c) 2013 Derek Petersen https://github.com/tuxracer/touchclick
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+Copyright (c) 2013 Derek Petersen https://github.com/tuxracer/touchclick MIT License
 ###
 ((factory) ->
   try


Couchmuffin tests fix

Couchmuffin tests fix

--- server/test/Component/Builtin/Database/CouchMuffin.coffee
+++ server/test/Component/Builtin/Database/CouchMuffin.coffee
@@ -34,6 +34,7 @@
                 value:
                     string: 'string'
                     number: 1
+                    _id: 'MyKey'
         stdMyError =
             name: 'MyError'

@@ -151,7 +152,7 @@
             instance.init()
             instance.findManyById ids: ['MyKey','MyKey1'], (error, result) ->
                 expect(error).not.to.be.ok()
-                expect(result).to.be '': stdResult.MyKey
+                expect(result).to.be.eql '' : stdResult.MyKey
                 done()

         it 'should pass the error to the callback if something occurs', (done) ->


Added adapter method and model factory

Added adapter method and model factory

--- dev/angular-database.coffee
+++ dev/angular-database.coffee
@@ -15,6 +15,12 @@
             'AND WHERE',
             'OR WHERE'
         ]
+
+        adapter: {}
+
+        setAdapter: (adapter) ->
+            @adapter = adapter
+            return

         createTable: ->
             return
@@ -151,3 +157,11 @@

             @req += "UPDATE `" + update['TABLE'] + "` SET " + params
             return
+
+database.factory '$model', Array '$database', ->
+    new class Model
+        findAll: ->
+
+        find: ->
+
+        save: ->


colliderimage change

colliderimage change

--- lib/_stationary.coffee
+++ lib/_stationary.coffee
@@ -363,8 +363,7 @@
             if (!motionObj? || !motionObj.collider? || !motionObj.collider.sprite? || !@collider? || !@collider.sprite?)
                 ret = false
             else if (@intersectFlag == true && motionObj.intersectFlag == true)
-                if (!@rigid)
-                    ret = @collider.sprite.intersect(motionObj.collider.sprite)
+                ret = @collider.sprite.intersect(motionObj.collider.sprite)
             else
                 ret = false
         else if (@_type == MAP || @_type == EXMAP)


Ignore Errors in imported files

Ignore Errors in imported files

This fixes #2

--- lib/linter-less.coffee
+++ lib/linter-less.coffee
@@ -22,7 +22,7 @@
     parser = new(less.Parser)(options)
     parser.parse data, (err, tree) =>

-      return callback([]) unless err
+      return callback([]) if not err or err.filename is not filePath

       lineIdx = Math.max 0, err.line - 1

@@ -40,4 +40,4 @@
       return callback([]) if err
       @parseLessFile data, filePath, callback

-module.exports = LinterLess+module.exports = LinterLess


:minibus: Use the new editor-linter instead of the old one

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -5,7 +5,7 @@
 Panel = require './panel'
 PanelView = require './views/panel'
 Bottom = require './bottom'
-EditorLinter = require './editor-linter-old'
+EditorLinter = require './editor-linter'

 class Linter


added lat and lon to stops

added lat and lon to stops

--- lib/models/stop.coffee
+++ lib/models/stop.coffee
@@ -18,6 +18,8 @@
   # ).property()
   arriveTime: ( -> @getPath('journey.messages.lastObject.datetime') ).property()
   address:    ( -> @getPath('journey.messages.lastObject.address') ).property()
+  lat:    ( -> @getPath('journey.messages.lastObject.lat') ).property()
+  lon:    ( -> @getPath('journey.messages.lastObject.lon') ).property()
   nextJourney: ->
     journey = @get('journey')
     journeys = @getPath('journey.vehicle.journeys').slice(0)


Suggest launching atom from the command line

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -88,6 +88,8 @@
     @output.append $$ ->
       @h1 'Unable to run'
       @pre _.escape command
+      @h2 'Did you start Atom from the command line?'
+      @pre '  atom .'
       @h2 'Is it in your PATH?'
       @pre "PATH: #{_.escape process.env.PATH}"


remove extra noConflict

remove extra noConflict

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -18,7 +18,7 @@

   # binding the libs that bokeh uses so others can reference them
   Bokeh._                 = require("underscore")
-  Bokeh.$                 = require("jquery").noConflict(true)
+  Bokeh.$                 = require("jquery")
   Bokeh.Backbone          = require("backbone")

   # common


Admin config :)

Admin config :)

--- both/config/adminConfig.coffee
+++ both/config/adminConfig.coffee
@@ -6,10 +6,20 @@
 		Posts: {
 			color: 'red'
 			icon: 'pencil'
+			tableColumns: [
+              {label: 'Title',name:'title'}
+              {label:'User',name:'owner',collection:'Users'}
+            ]
 		}
 		Comments: {
 			color: 'green'
 			icon: 'comments'
+			auxCollections: ['Posts']
+			tableColumns: [
+              {label: 'Content';name:'content'}
+              {label:'Post';name:'doc',collection: 'Posts',collection_property:'title'}
+              {label:'User',name:'owner',collection:'Users'}
+            ]
 		}
 	dashboard:
 		homeUrl: '/dashboard'


Consistent style

Consistent style

--- js/jack-in-the-box.coffee
+++ js/jack-in-the-box.coffee
@@ -12,7 +12,7 @@
     @settings = {}

     # jQuery version of DOM element attached to the plugin
-    @$element = $ element
+    @$element = $(element)

     # Check if box is visible
     @visible = ($box) =>
@@ -50,7 +50,7 @@
     @init = ->
       @settings = $.extend({}, @defaults, options)

-      @$window  = $ window
+      @$window  = $(window)
       @$boxes   = $(".#{@settings.boxClass}").css(visibility: 'hidden')

       if @$boxes.length


VDI name/description edition are now working in the SR view

--- app/modules/sr/index.coffee
+++ app/modules/sr/index.coffee
@@ -1,4 +1,5 @@
 angular = require 'angular'
+isEmpty = require 'isempty'

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

@@ -11,6 +12,7 @@
       controller: 'SrCtrl'
       template: require './view'
   .controller 'SrCtrl', ($scope, $stateParams, $q, xoApi, xo, modal) ->
+    {get} = xo
     $scope.$watch(
       -> xo.revision
       -> $scope.SR = xo.get $stateParams.id


use $window rather than window

use $window rather than window

--- lineman/app/services/intercom_service.coffee
+++ lineman/app/services/intercom_service.coffee
@@ -1,12 +1,12 @@
-angular.module('loomioApp').factory 'IntercomService', ($rootScope, $http, AppConfig, CurrentUser, $location, LmoUrlService) ->
+angular.module('loomioApp').factory 'IntercomService', ($rootScope, $window, AppConfig, CurrentUser, LmoUrlService) ->
   currentGroup = null
   service = new class IntercomService
     available: ->
-      window? and window.Intercom?
+      $window? and $window.Intercom?

     boot: ->
       return unless @available()
-      window.Intercom 'boot',
+      $window.Intercom 'boot',
        app_id: AppConfig.intercomAppId
        user_id: CurrentUser.id
        user_hash: AppConfig.intercomUserHash
@@ -18,13 +18,13 @@

     shutdown: ->
       return unless @available()
-      window.Intercom('shutdown')
+      $window.Intercom('shutdown')

     updateWithGroup: (group) ->
       return if currentGroup == group
       currentGroup = group
       return unless @available()
-      window.Intercom 'update',
+      $window.Intercom 'update',
         email: CurrentUser.email
         user_id: CurrentUser.id
         company:
@@ -41,9 +41,9 @@

     contactUs: ->
       if @available()
-        window.Intercom.public_api.showNewMessage()
+        $window.Intercom.public_api.showNewMessage()
       else
-        window.location = LmoUrlService.contactForm()
+        $window.location = LmoUrlService.contactForm()

   $rootScope.$on 'analyticsSetGroup', (event, group) ->
     service.updateWithGroup(group)


Remove console.logs

Remove console.logs

--- app/assets/javascripts/form/dates.js.coffee
+++ app/assets/javascripts/form/dates.js.coffee
@@ -21,21 +21,18 @@
     $element = $(element)
     return if $element.is('[data-flatpickr="false"]')
     options = baseDateOptions $element
-    console.log "date", options
     $element
       .flatpickr options

   enableDatetimePicker = (element) =>
     $element = $(element)
     options = baseDateTimeOptions $element
-    console.log "datetime", options
     $element
       .flatpickr options

   enableDateRangePicker = (element) =>
     $element = $(element)
     options = baseDateRangeOptions $element
-    console.log "daterange", options
     $element
       .attr 'type', 'text'
       .flatpickr options


update add member notification

update add member notification

--- app/assets/javascripts/teams_show.js.coffee
+++ app/assets/javascripts/teams_show.js.coffee
@@ -37,5 +37,9 @@
       else
         node.addClass('alert-error')
       node.insertBefore('p.alert.alert-info')
+      setTimeout  ()->
+        node.hide('slow').remove()
+      ,4000
+
   $('.do-add').click ->
     $('.add-member-form').ajaxSubmit options


Closing a named window should remove tab by that name.

--- lib/zombie/tabs.coffee
+++ lib/zombie/tabs.coffee
@@ -91,7 +91,7 @@
     find:
       value: (name)->
         if tabs.propertyIsEnumerable(name)
-          return this[name]
+          return tabs[name]
         for window in this
           if window.name == name
             return window
@@ -127,6 +127,8 @@
     if ~index
       browser.emit("inactive", window)
       tabs.splice(index, 1)
+      if tabs.propertyIsEnumerable(window.name)
+        delete tabs[window.name]
       # If we closed the currently open tab, need to select another window.
       if window == current
         # Don't emit inactive event for closed window.


fix missing cb’s

fix missing cb’s

--- app/coffee/ProjectPersistenceManager.coffee
+++ app/coffee/ProjectPersistenceManager.coffee
@@ -61,8 +61,8 @@
 		job = (cb)->
 			console.log("_clearProjectFromDatabase")
 			db.Project.destroy(where: {project_id: project_id})
-				.then(() -> callback())
-				.error callback
+				.then(() -> cb())
+				.error cb
 		dbQueue.queue.push(job, callback)

@@ -71,8 +71,8 @@
 			console.log("_findExpiredProjectIds")
 			db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)])
 				.then((projects) ->
-					callback null, projects.map((project) -> project.project_id)
-				).error callback
+					cb null, projects.map((project) -> project.project_id)
+				).error cb
 		dbQueue.queue.push(job, callback)


zooming and dragging around

zooming and dragging around

Former-commit-id: b05711f27c3c50ffae6ab6e19c56f0fb2385fcd2 [formerly f1fbc1a079cc7e51dac93440ab407866032dd225]
Former-commit-id: 6aa22d24650511ae97308fc809a813b3df930444

--- js/msa/stage/tileStage.coffee
+++ js/msa/stage/tileStage.coffee
@@ -11,67 +11,69 @@
       @tilesX = Math.ceil(@canvas.width / @tileSize)
       @tilesY = Math.ceil(@canvas.height / @tileSize)

-      console.log "#tiles x:" + @tilesX + ",y:" + @tilesY
+      #console.log "#tiles x:" + @tilesX + ",y:" + @tilesY

       @canvas.addEventListener "mousemove", (e) =>
-        @_onmousemove e
+        @_onMouseMove e

       @canvas.addEventListener "dblclick", (e) =>
         @_onDblClick e

+      @canvas.addEventListener "contextmenu", (e) =>
+        @_onContextMenu e
+
       @canvas.addEventListener "mousedown", (e) =>
-        return
-        @_onmousedown e
+        @_onMouseDown e
+
       @canvas.addEventListener "mouseup", (e) =>
-        @_onmouseup e
+        @_onMouseUp e

       @canvas.addEventListener "mouseout", (e) =>
-        @_onmouseout e
+        @_onMouseOut e
+
+      @events = {}

       @map = []

       @viewportX = 120
       @viewportY = 100
-      @dblClickVx = 1
-      @dblClickVy = 1
+      @dblClickVx = 2
+      @dblClickVy = 2

       #@animate()
       @timestamp = 0

-    _onmousemove: (e) ->
+    _onMouseMove: (e) ->
       if @dragStartX? and @draglock?
-        distX= e.pageX - @dragStartX
-        distY = e.pageY - @dragStartY
-
-        @viewportX = @dragViewStartX - distX
-        @viewportY = @dragViewStartY - distY
-
-        @checkPos()
-
-        #@draw()
-        console.log "#viewx:" + @viewportX + ",y:" + @viewportY
-        console.log "#tiles x:" + distX + ",y:" + distY
+        @moveCanvasDragEvent e
+
+    _onMouseUp: (e) ->
+      if @dragStartX? and @draglock?
+        @moveCanvasDragEvent e
+
+      @draglock = undefined
+
+    _onMouseOut: (e) ->
+      @draglock = undefined
+
+    _onMouseDown: (e) ->
+      [mouseX,mouseY]= getMouseCoords e
+
+      unless @checkForEvents mouseX,mouseY
         @pauseEvent e
-
-    _onmouseup: (e) ->
-      @draglock = undefined
-
-    _onmouseout: (e) ->
-      @draglock = undefined
-
-    _onmousedown: (e) ->
-      @pauseEvent e
-      @dragStartX = e.pageX
-      @dragStartY = e.pageY
-      @dragViewStartX = @viewportX
-      @dragViewStartY = @viewportY
-      @draglock = true
-
-    _onDblClick: (e) ->
-
-      #if not @rect? or @rect?.left is 0
-      #  @rect = @canvas.getBoundingClientRect()
-
+        @dragStartX = e.pageX
+        @dragStartY = e.pageY
+        @dragViewStartX = @viewportX
+        @dragViewStartY = @viewportY
+        @draglock = true
+
+    _onContextMenu: (e) ->
+      #TODO: only on dblclick
+      @zoomCanvas 1 / @dblClickVx, 1 / @dblClickVy
+      @draw()
+
+    # TODO: move to utils
+    getMouseCoords: (e) ->
       # center the view on double click
       mouseX = e.offsetX
       mouseY = e.offsetY
@@ -86,36 +88,71 @@
         return

       # TODO: else
-
-      centerX = Math.round( @viewportX + (mouseX) - (@canvas.width / 2) )
-      centerY = Math.round( @viewportY + (mouseY) - (@canvas.height / 2) )
-
-      height = @msa.zoomer.columnHeight
-      width = @msa.zoomer.columnWidth
-      @viewportX = centerX
-      @viewportY = centerY
-      @viewportX = Math.round(centerX / width * (width + @dblClickVx))
-      @viewportY = Math.round(centerY / height * (height + @dblClickVy))
-
-
-      @msa.zoomer.columnWidth += @dblClickVx
-      @msa.zoomer.columnHeight += @dblClickVy
-      @refreshZoom()
+      return [mouseX,mouseX]
+
+    _onDblClick: (e) ->
+      #if not @rect? or @rect?.left is 0
+      #  @rect = @canvas.getBoundingClientRect()
+      @draglock = undefined
+
+      [mouseX,mouseY]= getMouseCoords e
+
+      @moveCenter mouseX,mouseY
+      @zoomCanvas @dblClickVx,@dblClickVy
+      @draw()
+
+      #console.log "#mouse:" + mouseX + ",y:" + mouseY
+      #console.log "#viewix:" + @viewportX + ",y:" + @viewportY
+
+    moveCanvasDragEvent: (e) ->
+      distX = e.pageX - @dragStartX
+      distY = e.pageY - @dragStartY
+
+      @viewportX = @dragViewStartX - distX
+      @viewportY = @dragViewStartY - distY

       @checkPos()
-
       @draw()
-
-      @msa.log.log "viewportX:" + @viewportX + ",viewportY:" + @viewportY
-
-      @ctx.fillStyle = "#ff0000"
-      @ctx.fillRect mouseX,mouseY,10,10
-      @ctx.fillRect @canvas.width/2,0,1,@canvas.height
-
-      console.log "#mouse:" + mouseX + ",y:" + mouseY
-      #console.log "#center:" + centerX + ",y:" + centerY
-      console.log "#viewix:" + @viewportX + ",y:" + @viewportY
-      @draglock = undefined
+      @pauseEvent e
+
+    # moves the center of the canvas to relative x,y
+    moveCenter: (mouseX,mouseY) ->
+
+      @viewportX = Math.round( @viewportX + (mouseX) - (@canvas.width / 2) )
+      @viewportY = Math.round( @viewportY + (mouseY) - (@canvas.height / 2) )
+
+      #TODO: do we need to check here -> redundant with zoomer
+      @checkPos()
+
+    # this rescales the center for vx and vy
+    zoomCanvas: (vx, vy) ->
+
+      newVx = @msa.zoomer.columnWidth * vx
+      newVy = @msa.zoomer.columnHeight * vy
+
+      if newVx < 1 or newVy < 1
+        console.log "invalid zoom level - x:" + newVx + "y:" + newVy
+      else
+        if vx isnt 0
+          @viewportX = Math.round(@viewportX / @msa.zoomer.columnWidth * (newVy))
+        if vy isnt 0
+          @viewportY = Math.round(@viewportY / @msa.zoomer.columnHeight * (newVx))
+
+        @msa.zoomer.columnWidth += vx
+        @msa.zoomer.columnHeight += vy
+
+        @refreshZoom()
+
+        #TODO: do we need to check here -> redundant with zoomer
+        @checkPos()
+
+    # goes through all events all checks for callbacks
+    checkForEvents: (mouseX, mouseY) ->
+      for name,arr of @events
+
+        if (arr[0] <= mouseX and mouseX <= ar[2]) and
+        (arr[1] <= mouseY and mouseY <= arr[3])
+          arr[4]()

     checkPos: ->
       [@viewportX,@viewportY] = @_checkPos @viewportX,@viewportY
@@ -239,8 +276,6 @@
           else
             tile = @drawTile mapX,mapY

-          #@ctx.fillStyle = "red"
-          #@ctx.fillRect i * @tileSize, j * @tileSize,@tileSize,@tileSize
           #@ctx.fillRect tileX,tileY,@tileSize,@tileSize
           #@ctx.putImageData tile,0,0,tileX,tileY,@tileSize,@tileSize

@@ -248,6 +283,18 @@
           #console.log "tile i:" + tileX + ",j:" + tileY
           @ctx.putImageData tile,tileX,tileY
           #@ctx.drawImage tile,tileX,tileY
+
+      # control overlays
+
+      # zoom control
+      @ctx.fillStyle = "red"
+      @ctx.fillRect @canvas.width - 40, @canvas.height - 35,15,15
+      callback = -> alert "hi"
+      @events.zoomIn = [@canvas.width - 40, @canvas.height - 35,15,15,callback]
+
+      @ctx.fillStyle = "blue"
+      @ctx.fillRect @canvas.width - 40, @canvas.height - 20,15,15
+      @events.zoomOut = [@canvas.width - 40, @canvas.height - 20,15,15,callback]

       return @canvasWrapper

@@ -331,6 +378,7 @@
       @canvas.id = "can"
       @ctx = @canvas.getContext "2d"
       @canvas.setAttribute "id","#{@globalID}_canvas"
+      @canvas.style.cursor  = "move"

       @canvasWrapper = document.createElement "div"
       @canvasWrapper.appendChild @canvas


[backport] fix #1792 - fix edit us submit button text

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -257,7 +257,7 @@
             })

             # Update texts for creation
-            $el.find(".button-green span").html("Create") #TODO: i18n
+            $el.find(".button-green").html("Create") #TODO: i18n
             $el.find(".title").html("New user story  ") #TODO: i18n

             $el.find(".blocked-note").addClass("hidden")
@@ -272,7 +272,7 @@
             $scope.isNew = false

             # Update texts for edition
-            $el.find(".button-green span").html("Save") #TODO: i18n
+            $el.find(".button-green").html("Save") #TODO: i18n
             $el.find(".title").html("Edit user story  ") #TODO: i18n

             # Update requirement info (team, client or blocked)


Updated settings with several fixes (#111)

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -168,8 +168,7 @@
 	# between services that may need to go over public channels
 	httpAuthUsers: httpAuthUsers

-	# Should javascript assets be served minified or not. Note that you will
-	# need to run `make minify` within the web directory to generate these.
+	# Should javascript assets be served minified or not.
 	useMinifiedJs: true

 	# Should static assets be sent with a header to tell the browser to cache
@@ -197,6 +196,10 @@
 			url: "http://localhost:3000"
 			user: httpAuthUser
 			pass: httpAuthPass
+		# overrides v1.url to indicate via Feature Flags that Overleaf V1
+		# is not available
+		v1:
+			url: null
 	references:{}
 	notifications:undefined

@@ -421,8 +424,6 @@
 			entryPoint: process.env["SHARELATEX_SAML_ENTRYPOINT"]
 			callbackUrl: process.env["SHARELATEX_SAML_CALLBACK_URL"]
 			issuer: process.env["SHARELATEX_SAML_ISSUER"]
-			cert: process.env["SHARELATEX_SAML_CERT"]
-			privateCert: process.env["SHARELATEX_SAML_PRIVATE_CERT"]
 			decryptionPvk: process.env["SHARELATEX_SAML_DECRYPTION_PVK"]
 			signatureAlgorithm: process.env["SHARELATEX_SAML_SIGNATURE_ALGORITHM"]
 			identifierFormat: process.env["SHARELATEX_SAML_IDENTIFIER_FORMAT"]
@@ -482,6 +483,11 @@
 					undefined
 			)

+	# SHARELATEX_SAML_CERT cannot be empty
+	# https://github.com/bergie/passport-saml/commit/f6b1c885c0717f1083c664345556b535f217c102
+	if process.env["SHARELATEX_SAML_CERT"]
+		settings.saml.server.cert = process.env["SHARELATEX_SAML_CERT"]
+		settings.saml.server.privateCert = process.env["SHARELATEX_SAML_PRIVATE_CERT"]

 # Compiler
 # --------


Improved compatibility of autocomplete with latest Ember

--- packages/emberella/lib/views/autocomplete.coffee
+++ packages/emberella/lib/views/autocomplete.coffee
@@ -53,7 +53,6 @@
     @final
   ###
   isAutocomplete: true
-

   ###
     @property childViews
@@ -511,7 +510,7 @@
     else
       set(@, key, value)
       return @
-  .property('hasFocus', '_isListVisible', 'suggestions.length', 'displayValue').volatile()
+  .property('hasFocus', '_isListVisible', 'suggestions.length', 'displayValue')

   ###
     The debounced method for responding to changes in the input value.
@@ -831,19 +830,6 @@
   ###
   _searchExpression: Ember.computed ->
     @expressionFor get(@, 'search')
-  .property('_escaped_search').readOnly()
-
-  ###
-    @private
-
-    The `search` string escaped for use as a regular expression.
-
-    @property _escaped_search
-    @type String
-    @readOnly
-  ###
-  _escaped_search: Ember.computed ->
-    @escapeSearch get(@, 'search')
   .property('search').readOnly()

   ###
@@ -867,7 +853,7 @@
   ###
   _hasFocusDidChange: Ember.observer ->
     if !get(@, 'hasFocus')
-      displayValue = get(@, 'displayValue')
+      displayValue = get(@, 'displayValue') ? ''
       selected = get(@, 'selected')
       # The field may appear to lose focus frequently as the focus shifts
       # between child views. The run later helps to verify the focus has, in
@@ -1037,8 +1023,7 @@
   @uses Emberella.MembershipMixin
 ###
 Emberella.AutocompleteListView = Ember.CollectionView.extend Emberella.MembershipMixin,
-  isVisibleBinding: 'leadView.isListVisible'
-  inherit: ['itemViewClass', 'content:suggestions']
+  inherit: ['itemViewClass', 'content:suggestions', 'isVisible:isListVisible']
   classNames: ['emberella-autocomplete-list']


test(engine): increase timeout in execute wait

--- packages/engine/test/actions/execute/wait.coffee
+++ packages/engine/test/actions/execute/wait.coffee
@@ -108,13 +108,13 @@
         @call ->
           setTimeout ->
             nikita(ssh: ssh?.config).fs.mkdir "#{tmpdir}/file_1"
-          , 30
+          , 100
           setTimeout ->
             nikita(ssh: ssh?.config).fs.mkdir "#{tmpdir}/file_2"
-          , 60
+          , 200
           setTimeout ->
             nikita(ssh: ssh?.config).fs.mkdir "#{tmpdir}/file_3"
-          , 90
+          , 300
         {status} = await @execute.wait
           command: [
             "test -d #{tmpdir}/file_1 && echo 1 >> #{tmpdir}/result"


Limit max publication slug length.

Limit max publication slug length.

--- server/publication.coffee
+++ server/publication.coffee
@@ -2,12 +2,14 @@

 NUMBER_OF_VERIFICATION_SAMPLES = 3
 VERIFICATION_SAMPLE_SIZE = 64
+
+SLUG_MAX_LENGTH = 80

 class @Publication extends @Publication
   @MixinMeta (meta) =>
     meta.fields.slug.generator = (fields) ->
       if fields.title
-        [fields._id, URLify2 fields.title]
+        [fields._id, URLify2 fields.title SLUG_MAX_LENGTH]
       else
         [fields._id, '']
     meta


Add logic for handling Quantity intervals When doing a compare on Quantities with a duration convert that duration to milliseconds so that different durations can be compared.

--- Src/coffeescript/cql-execution/src/elm/quantity.coffee
+++ Src/coffeescript/cql-execution/src/elm/quantity.coffee
@@ -28,15 +28,30 @@
     if other instanceof Quantity and other.unit == @unit then @value > parseFloat other.value else null

   before: (other) ->
-    if other instanceof Quantity and other.unit == @unit then @value < parseFloat other.value else null
+    if other instanceof Quantity and other.unit == @unit
+      @value < parseFloat other.value
+    else if other instanceof Quantity and other.unit of time_units and @unit of time_units
+      thisSmallestDuration = smallestDuration(@)
+      otherSmallestDuration = smallestDuration(other)
+      thisSmallestDuration < otherSmallestDuration
+    else null

   equals: (other) ->
     if other.unit? && other.value? then @unit == other.unit && @value == parseFloat other.value else null

-time_units = {'years':'year',  'months': 'month',  'days' :'day', 'minutes': 'minute', 'seconds':'seconds', 'milliseconds' : 'millisecond' }
+time_units = {'years': 'year', 'yr': 'year', 'y': 'year', 'months': 'month', 'weeks': 'week', 'wk': 'week', 'wks': 'week', 'days': 'day', 'd': 'day', 'minutes': 'minute', 'min': 'minute', 'seconds':'second', 'sec':'second', 'second':'second', 'milliseconds' : 'millisecond' }

 clean_unit = (units) ->
   if time_units[units] then time_units[units] else units
+
+smallestDuration = (qty) ->
+  millivalue = switch
+    when clean_unit(qty.unit) == 'minute' then qty.value * 60000
+    when clean_unit(qty.unit) == 'hour' then qty.value * 3600000
+    when clean_unit(qty.unit) == 'day' then qty.value * 86400000
+    when clean_unit(qty.unit) == 'week' then qty.value * 604800000
+    else qty.value
+  millivalue

 module.exports.createQuantity = (value,unit) ->
   new Quantity({value: value, unit: unit})


ace: remove find & fold-widget commands

ace: remove find & fold-widget commands

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -116,9 +116,12 @@
         'gotoline'         # overriding this
         'sortlines'        # using same mapping for 'save all' action. XXX: re-map sortlines
         'showSettingsMenu' # f ace default settings menu
+        'find'
         'findprevious'     # we have our own find and replace dialogs
         'findnext'         # ace default for findnext is cmd+g (mapped to gotoline)
         'findAll'          # not used and collides with toggle filetree
+        'toggleFoldWidget'
+        'toggleParentFoldWidget'
         'restartSearch'
         'iSearch'
         'iSearchAndGo'


Hacken.in/Hamburg

Hacken.in/Hamburg

--- app/assets/javascripts/pages/welcome.coffee
+++ app/assets/javascripts/pages/welcome.coffee
@@ -32,6 +32,11 @@
     ruhrgebiet.setContent("Ruhrgebiet")
     map.addLayer(ruhrgebiet)

+    hamburg = new L.Popup()
+    hamburg.setLatLng([53.5653,10.0014])
+    hamburg.setContent("Hamburg")
+    map.addLayer(hamburg)
+
     map.fitBounds([
       [50.938056,6.956944],
       [52.516667,13.383333],


Add AJAX request to retrieve weighted score

The weighting floats are now collected from the modal input fields and a
JSON request is made to retrieve the data.

Signed-off-by: Konrad Reiche <4ff0213f56e2082fcd8c882d04c546881c579ce4@gmail.com>

--- app/assets/javascripts/repository.js.coffee
+++ app/assets/javascripts/repository.js.coffee
@@ -51,9 +51,13 @@
 # Update the metriiic scores according to the weighting
 updateScores = () ->
   $("#weighting-modal").on "hidden.bs.modal", () =>
+    weighting = {}
     for metric in gon.metrics
       weight = $("input[id^=#{metric}]").val()
-
+      if not not weight
+        weighting[metric] = weight
+    $.getJSON "#{window.location.pathname}/score", weighting, (data) =>
+      console.log data
 $ ->

   if getPaths()[0] == 'repository'


Improved syntax

Improved syntax

--- app/controllers/templates_controller.coffee
+++ app/controllers/templates_controller.coffee
@@ -59,88 +59,84 @@
                         newObj['app'].push(permissions.key)
                 jsonRes.push(newObj)

-
             #send json
             res.send(jsonRes)

 #search
 action 'search', ->
-    if req.query?
-        if req.query.doctype? and req.query.range? and req.query.page? and req.query.nbperpage?
+    if req.query? and req.query.doctype? and req.query.range? and \
+       req.query.page? and req.query.nbperpage?

-            #prepare params
-            pageParams = {}
-            page = parseInt(req.query.page, 10)
-            nbPerPage = parseInt(req.query.nbperpage, 10)
-
+        #prepare params
+        pageParams = {}
+        page = parseInt(req.query.page, 10)
+        nbPerPage = parseInt(req.query.nbperpage, 10)

-            #newKey = req.query.doctype.join('_')
-            # if not ds.pageCountMatrix[newKey]?
-            #     ds.pageCountMatrix[newKey] = []

-            #skip & limit + deleted lines
-            nbDeleted = if req.query.deleted? then parseInt(req.query.deleted, 10) else 0
-            pageParams['limit'] = nbPerPage
-            if page > 1
-                pageParams['skip'] = (nbPerPage * (page - 1)) - nbDeleted
+        #newKey = req.query.doctype.join('_')
+        # if not ds.pageCountMatrix[newKey]?
+        #     ds.pageCountMatrix[newKey] = []

-            #add query for plain text search
-            if req.query.query? and req.query.query isnt ""
-                pageParams['query'] = req.query.query
-
-            #if page is 1
-            requests = []
-            requests.push (callback) -> #0 -> metadoctypes
-                ds.getView callback, ds.getPATH().metadoctype.getallbyrelated
+        #skip & limit + deleted lines
+        nbDeleted = if req.query.deleted? then parseInt(req.query.deleted, 10) else 0
+        pageParams['limit'] = nbPerPage
+        if page > 1
+            pageParams['skip'] = (nbPerPage * (page - 1)) - nbDeleted

-            #one request per doctype
-            #reqCount = 0
-            #for dt in req.query.doctype
-            requests.push (callback) -> #1 to n -> requests
-                if pageParams['query']?
-                    ds.getView callback, ds.getPATH().search + req.query.doctype[0], pageParams
-                else
-                    ds.getView callback, ds.getPATH().request + req.query.doctype[0] + ds.getPATH().all, pageParams
-                #reqCount++
-
+        #add query for plain text search
+        if req.query.query? and req.query.query isnt ""
+            pageParams['query'] = req.query.query

-            async.parallel requests, (error, results) ->
-                jsonRes = []
-                if error
-                    res.send('no_result', 'No result : Server error occurred while retrieving data.')
-                    console.log error
-                else
-                    idField = []
-                    descField = []
-                    #for dt in req.query.doctype
-                    for md in results[0]
-                        if md.key? and md.value.identificationField? and md.key.toLowerCase() is req.query.doctype[0].toLowerCase()
-                            idField[req.query.doctype[0].toLowerCase()] = md.value.identificationField
-                            if md.value.fields[0]?
-                                descField[req.query.doctype[0].toLowerCase()] = md.value.fields[0]
-                    #for result, index in results
-                        #if index > 0
-                    for doc in results[1]
-                        if doc.key? and doc.value?
-                            doc.value['idField'] = idField[doc.value['docType'].toLowerCase()]
-                            doc.value['descField'] = descField[doc.value['docType'].toLowerCase()]
-                            jsonRes.push doc.value
+        #if page is 1
+        requests = []
+        requests.push (callback) -> #0 -> metadoctypes
+            ds.getView callback, ds.getPATH().metadoctype.getallbyrelated

-                    res.send(jsonRes)
+        #one request per doctype
+        #reqCount = 0
+        #for dt in req.query.doctype
+        requests.push (callback) -> #1 to n -> requests
+            if pageParams['query']?
+                ds.getView callback, ds.getPATH().search + req.query.doctype[0], pageParams
+            else
+                ds.getView callback, ds.getPATH().request + req.query.doctype[0] + ds.getPATH().all, pageParams
+            #reqCount++

-                    #page count matrix knows how many results must be skipped for each doctypes
-                    #ds.pageCountMatrix[newKey] = jsonRes

-                    #prepare limit
-                    #limit = if ds.pageCountMatrix[newKey].length? and ds.pageCountMatrix[newKey].length <= nbPerPage then ds.pageCountMatrix[newKey].length else nbPerPage
-                    #res.send(ds.pageCountMatrix[newKey].slice(0, limit))
-            # else if ds.pageCountMatrix[newKey] and ds.pageCountMatrix[newKey].length > nbPerPage
-            #     #send json
-            #     limit = if ds.pageCountMatrix[newKey].length? and ds.pageCountMatrix[newKey].length <= nbPerPage*page then ds.pageCountMatrix[newKey].length else nbPerPage*page
-            #     res.send(ds.pageCountMatrix[newKey].slice(nbPerPage*(page-1), limit))
-        else
-            res.send([{ 'no_result' : 'No result for now.' }])
-
+        async.parallel requests, (error, results) ->
+            jsonRes = []
+            if error
+                res.send('no_result', 'No result : Server error occurred while retrieving data.')
+                console.log error
+            else
+                console.log results
+                idField = []
+                descField = []
+                #for dt in req.query.doctype
+                for md in results[0]
+                    if md.key? and md.value.identificationField? and md.key.toLowerCase() is req.query.doctype[0].toLowerCase()
+                        idField[req.query.doctype[0].toLowerCase()] = md.value.identificationField
+                        if md.value.fields[0]?
+                            descField[req.query.doctype[0].toLowerCase()] = md.value.fields[0]
+                #for result, index in results
+                    #if index > 0
+                for doc in results[1]
+                    if doc.key? and doc.value?
+                        doc.value['idField'] = idField[doc.value['docType'].toLowerCase()]
+                        doc.value['descField'] = descField[doc.value['docType'].toLowerCase()]
+                        jsonRes.push doc.value
+                res.send(jsonRes)
+
+                #page count matrix knows how many results must be skipped for each doctypes
+                #ds.pageCountMatrix[newKey] = jsonRes
+
+                #prepare limit
+                #limit = if ds.pageCountMatrix[newKey].length? and ds.pageCountMatrix[newKey].length <= nbPerPage then ds.pageCountMatrix[newKey].length else nbPerPage
+                #res.send(ds.pageCountMatrix[newKey].slice(0, limit))
+        # else if ds.pageCountMatrix[newKey] and ds.pageCountMatrix[newKey].length > nbPerPage
+        #     #send json
+        #     limit = if ds.pageCountMatrix[newKey].length? and ds.pageCountMatrix[newKey].length <= nbPerPage*page then ds.pageCountMatrix[newKey].length else nbPerPage*page
+        #     res.send(ds.pageCountMatrix[newKey].slice(nbPerPage*(page-1), limit))
     else
         res.send([{ 'no_result' : 'No result for now.' }])


minor fix params

minor fix params

--- client/src/sidebar.coffee
+++ client/src/sidebar.coffee
@@ -94,7 +94,7 @@
                     input {
                         id: "vflatlng"
                         type: 'text',
-                        placeholder: "#{@state.hlat}, #{@state.hlng}"}
+                        placeholder: "#{@state.lat}, #{@state.lng}"}

                     br null, null
                     "zomm: "
@@ -102,7 +102,7 @@
                     input {
                         id: "vfzoom",
                         type: 'text',
-                        placeholder: "#{@state.hzoom}"}
+                        placeholder: "#{@state.zoom}"}

                     br null, null
                     br null, null


Fixed log stretch to handle values less than 0

--- fits/web_gl.coffee
+++ fits/web_gl.coffee
@@ -46,12 +46,18 @@

         "varying vec2 v_textureCoord;",

+        "float logarithm(float value) {",
+            "return log(value / 0.05 + 1.0) / log(1.0 / 0.05 + 1.0);",
+        "}",
+
         "void main() {",
             "vec4 pixel_v = texture2D(u_tex, v_textureCoord);",

-            "float min = log(u_extremes[0]);",
-            "float max = log(u_extremes[1]);",
-            "float pixel = (log(pixel_v[0]) - min) / (max - min);",
+            "float min = logarithm(u_extremes[0]);",
+            "float max = logarithm(u_extremes[1]);",
+            "float pixel = logarithm(pixel_v[0]);",
+
+            "pixel = (pixel - min) / (max - min);",

             "gl_FragColor = vec4(pixel, pixel, pixel, 1.0);",
         "}"


cleaning up

cleaning up

--- js/src/http/ObjectApi.coffee
+++ js/src/http/ObjectApi.coffee
@@ -55,8 +55,8 @@
   objectTreeNextPageUrl = ({ objectKey, pageSize, latestObjectId, latestObjectVersion }) ->
     objectTreePagedUrl(objectKey, pageSize) + '/' + latestObjectId + '/' + latestObjectVersion

-  indexSegmentUrl = -> objectUrl() + '/index-segment'
-  bulkIndexSegmentsUrl = (objectId, objectVersion) -> objectVersionUrl(objectId, objectVersion) + '/index-segments'
+  bulkIndexSegmentsUrl = (objectId, objectVersion) ->
+    objectVersionUrl(objectId, objectVersion) + '/index-segments'

   class ObjectApi

@@ -213,24 +213,6 @@
         else
           return null

-    createIndexSegment: (createIndexSegmentRequest) ->
-      Promise.resolve(
-        axios(
-          Requests.wrapCredentials({
-            method  : 'POST'
-            url     : indexSegmentUrl()
-            data    : JSON.stringify(createIndexSegmentRequest)
-            headers : DEFAULT_HEADERS
-          })
-        )
-      )
-      .then (axiosResponse) ->
-        if axiosResponse and axiosResponse.data
-          # axiosResponse.data == com.kryptnostic.v2.storage.models.VersionedObjectKey
-          return axiosResponse.data
-        else
-          return null
-
     createIndexSegments: (parentObjectKey, indexSegmentRequests) ->
       Promise.resolve(
         axios(


Removeu unneeded dependency

Removeu unneeded dependency

--- lib/actions/device.coffee
+++ lib/actions/device.coffee
@@ -1,4 +1,3 @@
-_ = require('lodash')
 deviceModel = require('../models/device')
 getDeviceDisplayName = require('../device/device').getDisplayName
 log = require('../log/log')


Clean up ad debug stuff

Clean up ad debug stuff

--- assets/js/panes/info.coffee
+++ assets/js/panes/info.coffee
@@ -23,8 +23,6 @@

 module.run [ "$timeout", "panes", "session", "activity", "url", ($timeout, panes, session, activity, url) ->
-
-  adGen = 0

   panes.add
     id: "info"
@@ -77,22 +75,10 @@
     link: ($scope, $el, attrs) ->
       pane = @

-      $scope.adId = 'freestar-info-' + (adGen++)
-
-      # $scope.$watch ( -> pane.active), (active) ->
-      #   if active and !$scope.adcode and url.carbonadsH
-      #     $scope.adcode = true
-      #     $timeout -> $script(url.carbonadsH)
-      #   if active and $scope.adcode
-      #     $timeout ->
-
       $scope.session = session

       $scope.$watch "session.plunk.id", (id) ->
-        pane.hidden = false# || !id
-
-      # $scope.$watch "adcode", adcode ->
-      #   freestar.queue.push(() -> googletag.display(
+        pane.hidden = !id

       $textarea = $(".plunker-comment-box textarea", $el).autosize(append: "\n").css("height", "24px")
       $textarea.on "blur", -> $textarea.css("height", "24px")
@@ -107,8 +93,6 @@
   link: ($cope, $el, $attrs) ->
     adId = 'plnkr-ad-' + adGen++

-    console.log('setting innerHtml', $el[0])
-
     $el[0].innerHTML = """
       <div align="center" id="div-gpt-ad-300x250">
         <script data-cfasync="false" type='text/javascript'>


Implemented file building.

Implemented file building.

--- lib/coffee-mug/builder.coffee
+++ lib/coffee-mug/builder.coffee
@@ -120,14 +120,44 @@
             console.log "Copying #{f}..."
             util.pump fs.createReadStream(sourceFile), fs.createWriteStream(targetFile)
     # Prune orphaned files
+    buildFileNames = []
+    for bf in _.keys @files
+      buildFileNames.push path.join(@compilePath, version, bf)
     walkdir.sync path.join(@compilePath, version), (f, stat) ->
       return if stat.isDirectory()
-      if targets.indexOf(f) is -1
+      if targets.indexOf(f) is -1 and buildFileNames.indexOf(f) is -1
         console.log "Removing orphaned file #{f}..."
         fs.unlinkSync f
   buildFiles: (version) ->
     version = @defaultVersion unless version?
     throw "#{version} is not a valid version name" unless @versions[version]?
+    builtFiles = []
+    compiledFiles = []
+    for c, f of @generateCompileConfigTree() when not f.ignore
+      compiledFiles.push f.targetFile(c)
+    for bf, bfConf of @files
+      # Check if we need update, searching sources one by one to get correct
+      # dependency order
+      bfPath = path.join @compilePath, version, bf
+      sources = [bfConf.source] unless _.isArray bfConf.source
+      matchedFiles = []
+      requiresBuild = false
+      for s in sources
+        for cf in compiledFiles when (matchedFiles.indexOf cf is -1) and
+        @matchPath cf, s
+          cfPath = path.join @compilePath, version, cf
+          throw "Cannot find compiled file #{cf}" unless path.existsSync cfPath
+          matchedFiles.push cfPath
+          if not requiresBuild and (not path.existsSync(bfPath) or
+          fs.statSync(cfPath).mtime > fs.statSync(bfPath).mtime)
+            requiresBuild = true
+      if requiresBuild
+        console.log "Building #{bf}..."
+        mkdirp.sync path.dirname(bfPath)
+        bfFile = fs.openSync bfPath, 'w'
+        for mf in matchedFiles
+          fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
+      compiledFiles.push bf
   postProcess: (version) ->
     version = @defaultVersion unless version?
     throw "#{version} is not a valid version name" unless @versions[version]?


Removed coffee script

Removed coffee script

--- lib/color-highlighting.coffee
+++ lib/color-highlighting.coffee
@@ -1,83 +0,0 @@
-$ = require("atom")
-$ = $.$
-module.exports = activate: (state) ->
-  getValue = (element, style) ->
-    window.getComputedStyle(element).getPropertyValue style
-
-  rgbStringToValues = (rgb) ->
-    sliceNum = 4
-    sliceNum = 5  if /rgba|hsla/.test(rgb)
-    rgb.slice(sliceNum).slice(0, -1).split(",").map (value) ->
-      parseInt value
-
-
-  contrast = (color) ->
-    r = color[0]
-    g = color[1]
-    b = color[2]
-    yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000
-    (if (yiq >= 128) then "#000" else "#fff")
-
-  applyColor = ->
-
-    # CSS hexa and rgb
-    atom.workspaceView.find(".css.color, .rgb-value, .w3c-standard-color-name").each (i, el) ->
-      colorType = $(this).prevAll(".support.function").text()
-      if colorType is "rgb" or colorType is "rgba"
-        el.style.backgroundColor = colorType + "(" + el.innerText + ")"
-      else
-        el.style.backgroundColor = el.innerText
-      backgroundColor = getValue(el, "background-color")
-      el.style.color = contrast(rgbStringToValues(backgroundColor))
-      return
-
-
-    # CSS hsl
-    atom.workspaceView.find(".meta.property-value.css").each (i, el) ->
-      colorType = $(this).find(".support.function, .misc.css").text()
-      selectorCache = $(this).find(".numeric.css")
-      if colorType is "hsl" or colorType is "hsla"
-        temp = ""
-        hslValues = selectorCache.each(->
-          temp += $(this).text() + ","
-          return
-        )
-        if temp.length
-          temp = temp.slice(0, temp.length - 1)
-          selectorCache.each ->
-            $(this)[0].style.backgroundColor = colorType + "(" + temp + ")"
-            backgroundColor = getValue(this, "background-color")
-            $(this)[0].style.color = contrast(rgbStringToValues(backgroundColor))
-            return
-
-      return
-
-
-    # LESS rgb
-    atom.workspaceView.find(".less").each ->
-      colorType = $(this).find(".builtin").text()
-      selectorCache = $(this).find(".numeric.css")
-      if colorType is "rgb" or colorType is "rgba"
-        temp = ""
-        rgbValues = selectorCache.each(->
-          temp += $(this).text() + ","
-          return
-        )
-        if temp.length
-          temp = temp.slice(0, temp.length - 1)
-          selectorCache.each ->
-            $(this)[0].style.backgroundColor = colorType + "(" + temp + ")"
-            backgroundColor = getValue(this, "background-color")
-            $(this)[0].style.color = contrast(rgbStringToValues(backgroundColor))
-            return
-
-      return
-
-    return
-
-  applyColor()
-  atom.workspaceView.eachEditorView (editorView) ->
-    editorView.on "editor:display-updated", applyColor
-    return
-
-  return


made the utils.showsavedialog use new finder

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -492,7 +492,7 @@
           style     : "solid green medium"
           callback  : => callback input, finderController, dialog
         Cancel      :
-          style     : "solid medium"
+          style     : "solid medium nobg"
           callback  : =>
             finderController.stopAllWatchers()
             finderController.destroy()
@@ -518,9 +518,11 @@
     dialog.show()
     input.setFocus()

-    finderController    = new NFinderController
-      nodeIdPath        : "path"
-      nodeParentIdPath  : "parentPath"
+    finderController = KD.singletons['appManager'].get('Finder').create
+      addAppTitle       : no
+      treeItemClass     : IDE.FinderItem
+      nodeIdPath        : 'path'
+      nodeParentIdPath  : 'parentPath'
       foldersOnly       : yes
       contextMenu       : no
       loadFilesOnInit   : yes


Hotfix: Ensure discussion reader id > 0, rather than not null

--- lineman/app/services/thread_query_service.coffee
+++ lineman/app/services/thread_query_service.coffee
@@ -43,7 +43,7 @@
     applyFilters = (view, filters, queryType) ->
       filters = [].concat filters

-      view.applyFind(discussionReaderId: { $ne: null })
+      view.applyFind(discussionReaderId: { $gt: 0 })

       switch queryType
         when 'important' then view.applyWhere (thread) -> thread.isImportant()


Event bubbling is used for the click handler

--- coffee/codesearch.coffee
+++ coffee/codesearch.coffee
@@ -1,29 +1,26 @@
-jQuery ->
+$ ->

     display_url = (id) ->
-        jQuery("#display-url").text().slice(0, -1) + id
+        $("#display-url").text().slice(0, -1) + id

     ajax_display_url = (id) ->
-        jQuery("#display-ajax-url").text().slice(0, -1) + id
+        $("#display-ajax-url").text().slice(0, -1) + id

-    search_url = jQuery('#search-url').text()
+    search_url = $('#search-url').text()

     basename = (path) ->
         splits = path.split('/')
         length = splits.length
         splits[splits.length - 1]

-    $("ul").on "click", "a", (event) ->
-
-        id = $(this).data('id')
+    popup = (link_target) ->
+        id = $(link_target).data('id')
         my_display_url = display_url(id)
         my_ajax_url = ajax_display_url(id)
-
-        title = basename $(event.target).text()
-
+        title = basename $(link_target).text()
         event.preventDefault()

-        jQuery.ajax
+        $.ajax
             url: my_ajax_url
             cache: false
             success: (data) ->
@@ -34,26 +31,32 @@
                 return
         return

+    $("ul").click (event) ->
+        if $(event.target).prop("tagName") is "A"
+            event.preventDefault()
+            popup(event.target)
+        return
+
     search = (event) ->
-        term = escape jQuery.trim $(event.target).val()
+        term = escape $.trim $(event.target).val()

         if not term
             $("ul").empty()
             return

         populate = (data) ->
-            jQuery("ul").empty()
+            $("ul").empty()
             for result in data
                 url = display_url(result.id)
                 li = "
#{result.path}
"
-                jQuery("ul").append li
+                $("ul").append li
             return

-        jQuery.ajax
+        $.ajax
             url: "#{search_url}?q=#{term}"
             dataType: "json"
             success: populate
         return

-    jQuery('input[type="search"]').keyup search
+    $('input[type="search"]').keyup search
     return


Add ?after query param validation

Add ?after query param validation

--- coffee/collection.coffee
+++ coffee/collection.coffee
@@ -18,7 +18,7 @@

 exports.get = (req, res, next) ->
   query = {}
-  query.endret = {$gte:req.query.after} if req.query.after
+  query.endret = {$gte:req.query.after} if typeof req.query.after is 'string'

   fields = {navn: true}


fixed fast start after pause

fixed fast start after pause

--- coffee/front/game.coffee
+++ coffee/front/game.coffee
@@ -84,6 +84,7 @@
     return

   start: ->
+    @last = Date.now()
     do @main_loop
     @started = yes
     do @msg_bottom.hide


implement hacky fetchRelated to solve null tool bug

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -90,6 +90,7 @@
     mod = Cu.Model.Dataset.findOrCreate box: box
     mod.fetch
       success: (model, resp, options) =>
+        model.fetchRelated()
         contentView = new Cu.View.DatasetOverview {model: model}
         subnavView = new Cu.View.DatasetNav {model: model}
         @appView.showView contentView


Fixed RPC dataset renaming

Fixed RPC dataset renaming

--- client/code/view/subnav.coffee
+++ client/code/view/subnav.coffee
@@ -232,6 +232,7 @@
     @toolsView = new Cu.View.DatasetTools
       model: @model
       view: @options.view
+    @model.on 'change', @render, this

   render: ->
     @$el.html """


Fixed issue on using `history_attrs`

Fixed issue on using `history_attrs`

Missing parenthesis...

--- muc/strophe.muc.coffee
+++ muc/strophe.muc.coffee
@@ -49,7 +49,7 @@
     .c("x", xmlns: Strophe.NS.MUC)

     if history_attrs?
-      msg = msg.c("history", history_attrs).up
+      msg = msg.c("history", history_attrs).up()

     if password?
       msg.cnode Strophe.xmlElement("password", [], password)


update I18NEditComponentView

update I18NEditComponentView

--- app/views/i18n/I18NEditComponentView.coffee
+++ app/views/i18n/I18NEditComponentView.coffee
@@ -21,6 +21,11 @@
             @wrapRow "#{propDoc.name} description (#{progLang})", ['description', progLang], description, i18n[lang]?[progLang]?.description, path, 'markdown'
         else if _.isString propDoc.description
           @wrapRow "#{propDoc.name} description", ['description'], propDoc.description, i18n[lang]?.description, path, 'markdown'
+        if _.isObject propDoc.shortDescription
+          for progLang, description of propDoc.shortDescription
+            @wrapRow "#{propDoc.name} shortDescription (#{progLang})", ['shortDescription', progLang], shortDescription, i18n[lang]?[progLang]?.shortDescription, path, 'markdown'
+        else if _.isString propDoc.description
+          @wrapRow "#{propDoc.name} shortDescription", ['shortDescription'], propDoc.shortDescription, i18n[lang]?.shortDescription, path, 'markdown'
         if context = propDoc.context
           for key, value of context
             @wrapRow "#{propDoc.name} context value", ['context', key], value, i18n[lang]?.context?[key], path