Adding support for the most recent version of Font Awesome.

--- src/plugins/lists.coffee
+++ src/plugins/lists.coffee
@@ -21,7 +21,7 @@
           editable: @options.editable
           label: label
           command: "insert#{type}List"
-          icon: "icon-list-#{label.toLowerCase()}"
+          icon: "fa-list-#{label.toLowerCase()}"
           cssClass: @options.buttonCssClass
         buttonset.append buttonElement


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -11,7 +11,4 @@

   player.include Flickerable

-  # Mock the keydown object
-  window.keydown = {}
-
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -1,7 +1,9 @@
 module "Flickerable"

 test 'it should set up defaults', ->
-
+  player = GameObject
+
+  player.include Flickerable

 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject


fix linux path for debian installed dart sdk

--- lib/flutter-toolkit.coffee
+++ lib/flutter-toolkit.coffee
@@ -22,7 +22,7 @@
   config:
     dartPath:
       type: 'string'
-      default: if os.platform() is 'win32' then 'C:\\Users\\gregs_000\\Downloads\\dartsdk-windows-x64-release\\dart-sdk\\bin\\dart.exe' else '/usr/local/bin/dart'
+      default: if os.platform() is 'win32' then 'C:\\Users\\gregs_000\\Downloads\\dartsdk-windows-x64-release\\dart-sdk\\bin\\dart.exe' else '/usr/bin/dart'
     debugPort:
       type: 'number'
       minium: 5857


fix tests

fix tests

--- desktop/apps/editorial_features/test/routes.coffee
+++ desktop/apps/editorial_features/test/routes.coffee
@@ -57,6 +57,7 @@
     @res = { render: sinon.stub(), locals: { sd: {CURRENT_USER: {email: 'mail@mail.com'}} }, redirect: sinon.stub() }
     @next = sinon.stub()
     routes.__set__ 'sd', {EF_VENICE: '123', EF_VIDEO_GUIDE: '456'}
+    routes.__set__ 'sailthru', sinon.stub()

   afterEach ->
     Backbone.sync.restore()


note sure why this was here

note sure why this was here

--- lib/connection/process/server.coffee
+++ lib/connection/process/server.coffee
@@ -136,7 +136,7 @@
           @ps[p.id] = p
           p.id

-    start: (path, args) -> cycler.start path, args, false
+    start: (path, args) -> cycler.start path, args

     onBoot: (id) => @ps[id].socket.then -> true
     onExit: (id) => new Promise (resolve) => @ps[id].onExit resolve


Teamwork: View active file and auto refresh features for preview pane is deprecated.

--- client/Teamwork/Views/teamworkworkspace.coffee
+++ client/Teamwork/Views/teamworkworkspace.coffee
@@ -26,30 +26,6 @@
         joinedUser = snapshot.name()
         return if not joinedUser or joinedUser is KD.nick()
         @hidePlaygroundsButton()
-
-      # if @amIHost()
-      #   # currently only for host.
-      #   # bc of clients need to know host's vmName and active pane's file data etc.
-      #   activePanel    = @getActivePanel()
-      #   {previewPane}  = activePanel.paneLauncher
-      #   {viewerHeader} = previewPane.previewer
-      #   viewerHeader.addSubView new KDButtonView
-      #     cssClass : "clean-gray tw-previewer-button"
-      #     title    : "View active file"
-      #     callback : =>
-      #       @previewFile()
-
-      #   viewerHeader.addSubView @autoRefreshSwitch = new KDOnOffSwitch
-      #     defaultValue : off
-      #     cssClass     : "tw-live-update"
-      #     title        : "Auto Refresh: "
-      #     size         : "tiny"
-      #     tooltip      :
-      #       title      : "If it's on, preview will be refreshed when you save a file."
-      #       placement  : "bottom"
-
-      #   activePanel.getPaneByName("editor").on "EditorDidSave", =>
-      #     @refreshPreviewPane previewPane  if @autoRefreshSwitch.getValue()

     @on "WorkspaceUsersFetched", =>
       @workspaceRef.child("users").once "value", (snapshot) =>


video: DRY up with enableVideo method

video: DRY up with enableVideo method

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -90,6 +90,8 @@
     @setState { connected: yes }
     @emit 'SessionConnected', session

+    @enableVideo { error: (err) => console.error err }  if helper.isVideoActive @channel
+

   ###*
    * Registers callbacks for service events.
@@ -155,11 +157,7 @@
         success : @bound 'handleStopSuccess'
         error   : (err) -> console.error err

-    session.on 'signal:start', =>
-      options = { publishAudio: @isMySession(), publishVideo: @isMySession() }
-      @startPublishing options,
-        success : @bound 'handlePublishSuccess'
-        error   : (err) -> console.error err
+    session.on 'signal:start', => @enableVideo { error: (err) => console.error err }

   subscribeToStream: (session, stream) ->


landing: remove obsolete app registrations

--- client/landing/site.landing/coffee/main.coffee
+++ client/landing/site.landing/coffee/main.coffee
@@ -4,12 +4,7 @@
 require './core/KD.extend.coffee'

 # register appclasses
-require './about/AppController'
-require './home/AppController'
 require './login/AppController'
-require './features/AppController'
-require './legal/AppController'
-require './pricing/AppController'
 require './team/AppController'
 require './teams/AppController'

@@ -23,14 +18,14 @@

     require './core/routes.coffee'
     require './login/routes.coffee'
-    require './pricing/routes.coffee'
-    require './legal/routes.coffee'
-    require './features/routes.coffee'
     require './teams/routes.coffee'
     require './team/routes.coffee'

   setGroup = (err, group) ->
     registerRoutes()
+    require './pricing/routes.coffee'
+    require './legal/routes.coffee'
+    require './features/routes.coffee'
     KD.config.group = group  if group
     # BIG BANG
     new MainController group


logic of redraw loop changed to constant loop

--- coffee/hyperbolic_tessellation_test_app.coffee
+++ coffee/hyperbolic_tessellation_test_app.coffee
@@ -246,19 +246,31 @@
   updatePopulation()

 frameRequested = false
+dirty = true
 redraw = ->
+  dirty = true
+  #avoid spamming frame requests for smoother movement.
+  #unless frameRequested
+  #  frameRequested = true
+  #  window.requestAnimationFrame ->
+  #    drawEverything()
+  #    frameRequested = false
+
+drawEverything = ->
   s = Math.min( canvas.width, canvas.height ) / 2
-  #avoid spamming frame requests for smoother movement.
-  unless frameRequested
-    frameRequested = true
-    window.requestAnimationFrame ->
-      frameRequested = false
-      context.clearRect 0, 0, canvas.width, canvas.height
-      context.save()
-      context.scale s, s
-      context.translate 1, 1
-      observer.draw cells, tfm, context
-      context.restore()
+  context.clearRect 0, 0, canvas.width, canvas.height
+  context.save()
+  context.scale s, s
+  context.translate 1, 1
+  observer.draw cells, tfm, context
+  context.restore()
+
+redrawLoop = ->
+  if dirty
+    drawEverything()
+    dirty = false
+  requestAnimationFrame redrawLoop
+

 toggleCellAt = (x,y) ->
   s = Math.min( canvas.width, canvas.height ) * 0.5
@@ -403,8 +415,9 @@
 updatePopulation = ->
   E('population').innerHTML = ""+cells.count

-redraw()
+#redraw()
 updatePopulation()
+redrawLoop()

 viewUpdates = 0
 #precision falls from 1e-16 to 1e-9 in 1000 steps.


Fix XSS vulnerability in room not found name.

--- packages/rocketchat-ui/views/404/roomNotFound.coffee
+++ packages/rocketchat-ui/views/404/roomNotFound.coffee
@@ -1,3 +1,6 @@
 Template.roomNotFound.helpers
 	data: ->
-		return Session.get 'roomNotFound'+		return Session.get 'roomNotFound'
+
+	name: ->
+		return Blaze._escape(this.name)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -8,7 +8,7 @@
 asyncTest "Active Animation", ->
   animation = Animation.fromPixieId 45

-  milliseconds = 200
+  milliseconds = 20

   setTimeout ->
     ok(animation.active(), "")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -6,7 +6,7 @@
       "frames": [0,1,2]
     ]

-  animation = Animation
+  animation = Animation(data)
     initialState: 'stand'

   animation.include(StateMachine)


relax benchmark range for travis

relax benchmark range for travis

--- test/performance.coffee
+++ test/performance.coffee
@@ -142,7 +142,7 @@
     results['nodejs'] = benchmark( ( -> test emitter ), 1, -> setup emitter )

     log 'emitting string events - many listeners bound x'+iterations, results
-    results.regexemitter.should.be.within (results.nodejs||1)*-100, (results.nodejs||1)*100
+    results.regexemitter.should.be.within (results.nodejs||1)*-300, (results.nodejs||1)*300

   it 'should compare removing all listeners', ->


rotatable tests

rotatable tests

--- test/rotatable.coffee
+++ test/rotatable.coffee
@@ -3,6 +3,8 @@
     rotationalVelocity: Math.PI / 4

   obj.include(Rotatable)
+
+  equals obj.I.rotation, 0, "Default rotation value is 0"

   2.times ->
     obj.update()


Escape the hyphens in the roles regexes

Escape the hyphens in the roles regexes

Inside square brackets, unescaped hyphens are position dependent. They
act like hyphens normally when at the beginning or end of a square
bracket expression. Otherwise it's used to signify a character range.

http://stackoverflow.com/questions/4068629/how-to-match-hyphens-with-regular-expression/4068725#4068725

--- src/scripts/roles.coffee
+++ src/scripts/roles.coffee
@@ -15,7 +15,7 @@
   getAmbiguousUserText = (users) ->
     "Be more specific, I know #{users.length} people named like that: #{(user.name for user in users).join(", ")}"

-  robot.respond /who is @?([\w .-]+)\?*$/i, (msg) ->
+  robot.respond /who is @?([\w .\-]+)\?*$/i, (msg) ->
     joiner = ', '
     name = msg.match[1].trim()

@@ -39,7 +39,7 @@
       else
         msg.send "#{name}? Never heard of 'em"

-  robot.respond /@?([\w .-_]+) is (["'\w: -_]+)[.!]*$/i, (msg) ->
+  robot.respond /@?([\w .\-_]+) is (["'\w: \-_]+)[.!]*$/i, (msg) ->
     name    = msg.match[1].trim()
     newRole = msg.match[2].trim()

@@ -63,7 +63,7 @@
         else
           msg.send "I don't know anything about #{name}."

-  robot.respond /@?([\w .-_]+) is not (["'\w: -_]+)[.!]*$/i, (msg) ->
+  robot.respond /@?([\w .\-_]+) is not (["'\w: \-_]+)[.!]*$/i, (msg) ->
     name    = msg.match[1].trim()
     newRole = msg.match[2].trim()


Remove automatically calls stopListening

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -30,6 +30,7 @@
     close: () ->
       @clean()
       delete @[key] for key of @
+      console.log @

   class Regions
     constructor: (regions = {}, $context) ->
@@ -75,7 +76,6 @@
         region.close()

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


Fix attribution view path

Fix attribution view path

--- src/scripts/modules/media/footer/footer.coffee
+++ src/scripts/modules/media/footer/footer.coffee
@@ -3,7 +3,7 @@
   MetadataView = require('cs!./metadata/metadata')
   DownloadsView = require('cs!./downloads/downloads')
   HistoryView = require('cs!./history/history')
-  AttributionView = require('cs!./metadata/metadata')
+  AttributionView = require('cs!./attribution/attribution')
   template = require('hbs!./footer-template')
   require('less!./footer')


Fix trivial code

Fix trivial code

--- test/spec_helper.coffee
+++ test/spec_helper.coffee
@@ -25,7 +25,7 @@
   results

 exports.registerSchema = ->
-  for schemaName in [arguments...]
+  for schemaName in arguments
     schemaString = fs.readFileSync "test/schemas/#{schemaName}.json", 'utf-8'
     schemaData = JSON.parse schemaString
     jsv.createSchema schemaData


Suicide after murder

Suicide after murder

--- src/setup/suicide.coffee
+++ src/setup/suicide.coffee
@@ -16,10 +16,12 @@

   wsh.Sleep 3000
   # Actual remove
-  try src.rm true

-  folder ini.load()
-  .rm true
+  try
+    folder ini.load()
+    .rm true
+
+  src.rm true

   exit 0


Stubs CommitPublisher methods in tests

Stubs CommitPublisher methods in tests

--- tests/infrastructure/commit_publisher.unit.coffee
+++ tests/infrastructure/commit_publisher.unit.coffee
@@ -120,6 +120,7 @@
       update: $push: { receivers: { appId: @appId, receivedAt: new Date() } }
     })
     @commitPublisher._markAsProcessed = ->
+    @commitPublisher._setProcessingTimeout = ->
     @commitPublisher.publishCommit(@commitPublisher._parseCommit(lockedCommit))
     @commitPublisher._failCommitProcessingAttempt(@commitId)

@@ -145,6 +146,7 @@

   it "stores each commit's publishing timeout using the id as a the key", ->
     commit = Commits.findOne(@commitId)
+    @commitPublisher._failCommitProcessingAttempt = ->
     @commitPublisher._setProcessingTimeout(commit)
     expect(@commitPublisher._inProgress).to.have.property(@commitId);
     expect(@commitPublisher._inProgress[@commitId]).to.respondTo('_onTimeout');


Form的逻辑性默认编辑方式变为Toggle

Form的逻辑性默认编辑方式变为Toggle

--- src/widget/edit/form.coffee
+++ src/widget/edit/form.coffee
@@ -5,14 +5,7 @@
 	@attributes:
 		bind:
 			setter: (bindStr) -> @_bindSetter(bindStr)
-		dataType:
-			setter: (dataType) ->
-				if dataType
-					if dataType instanceof cola.DataType
-						@_dataType = dataType
-					else
-						@_dataType = @_scope.dataType(dataType)
-				return
+		setter: cola.DataType.dataTypeSetter

 		defaultCols:
 			defaultValue: 3


abbrv months

abbrv months

--- slides/timeline/coffee/vis.coffee
+++ slides/timeline/coffee/vis.coffee
@@ -22,6 +22,7 @@
   xAxis = d3.svg.axis()
     .scale(xScale)
     .tickSize(-height)
+    .tickFormat(d3.time.format('%b'))
     # .tickFormat(d3.time.format('%b %Y'))
     # https://github.com/mbostock/d3/wiki/Time-Formatting


[skip] Hooked up the _modelHasBeenChanged event (1m)

--- vendor/assets/javascripts/neat/renderer/basic.coffee
+++ vendor/assets/javascripts/neat/renderer/basic.coffee
@@ -5,6 +5,7 @@
     @collection.bind 'sort',    @_collectionHasBeenSorted, @
     @collection.bind 'add',     @_modelHasBeenAddedToCollection, @
     @collection.bind 'remove',  @_modelHasBeenRemovedFromCollection, @
+    @collection.bind 'change',  @_modelHasBeenChanged, @
     @views = []
     @observer = new Observer()

@@ -85,5 +86,5 @@
   _modelHasBeenRemovedFromCollection: ->
     @_render()

-  _modelHasBeenChanged: ->
-    @_render()
+  _modelHasBeenChanged: (model) ->
+    # do nothing


Add extra models to test.

Add extra models to test.

--- test/javascripts/models/paper_test.js.coffee
+++ test/javascripts/models/paper_test.js.coffee
@@ -1,7 +1,9 @@
 moduleForModel 'paper', 'Unit: Paper Model',
   needs: ['model:author', 'model:user', 'model:figure', 'model:journal',
   'model:supportingInformationFile', 'model:phase', 'model:task', 'model:comment',
-  'model:litePaper', 'model:authorGroup', 'model:cardThumbnail', 'model:question', 'model:collaboration']
+  'model:litePaper', 'model:authorGroup', 'model:cardThumbnail', 'model:question',
+  'model:collaboration', 'model:affiliation', 'model:commentLook', 'model:flow', 'model:questionAttachment'
+  ]
   setup: -> setupApp()
   teardown: -> ETahi.reset()


See if this fixes test on Win.

See if this fixes test on Win.

--- test/spec/util.coffee
+++ test/spec/util.coffee
@@ -42,7 +42,7 @@
         spawned_env = cross_spawn_stub.args[0][2].env
         expect(cross_spawn_stub).to.have.been.calledWith bin
         expect(spawned_env)
-          .to.eql extend({ Path: new_env.PATH }, new_env)
+          .to.eql extend({}, new_env, { Path: new_env.PATH })

       describe "because of a weird failing behavious seen on windows", ->
         it "sets both Path and PATH to", ->


move node and all its children

move node and all its children

--- layout.coffee
+++ layout.coffee
@@ -87,6 +87,13 @@
             q.visit(collide(child))

+move = (node, dx, dy) ->
+    node.x += dx
+    node.y += dy
+    for child in node.children
+        move(child, dx, dy)
+
+
 collide = (node) ->
     r = node.radius + 50
     nx1 = node.x - r
@@ -103,10 +110,8 @@
             r = d3.max([node.width + other.width, node.height + other.height]) / 2
             if l < r  # found a collision
                 l = (l - r) / l * .5
-                node.x -= (dx *= l)
-                node.y -= (dy *= l)
-                other.x += dx
-                other.y += dy
+                move(node, - dx * l, - dy * l)
+                move(other, dx * l, dy * l)
         return x1 > nx2 or x2 < nx1 or y1 > ny2 or y2 < ny1

     return fn


allow options for remove (`soft` to only detach a tag)

--- src/xml.coffee
+++ src/xml.coffee
@@ -29,14 +29,17 @@
             if (listener = listeners[event])?
                 child.removeListener?(event, listener)
                 listeners[event] = undefined
-    remove = ->
-        dispose()
+    remove = (soft) ->
         if this is child
             parent.removeListener('removed', remove)
-        else
+            dispose()
+            parent.removeListener('replaced', replace)
+            child.removeListener('replaced', replace)
+        else unless soft
             child.removeListener('removed', remove)
-        parent.removeListener('replaced', replace)
-        child.removeListener('replaced', replace)
+            dispose()
+            parent.removeListener('replaced', replace)
+            child.removeListener('replaced', replace)
     replace = (tag) ->
         if this is child
             remove.call(parent)
@@ -70,6 +73,7 @@
         tag.emit? 'close', tag if tag.closed
         callback?.call(this, tag)

+    newtag.parent = this
     if @builder?
         @builder.approve('new', this, newtag, wire_tag)
     else
@@ -85,7 +89,6 @@

     TagInstance = @builder?.Tag ? Tag
     newtag = new TagInstance name, attrs, null, opts
-    newtag.parent = this
     add_tag.call this, newtag, (tag) ->
         tag.children children, opts if children?
     return newtag # hopefully this is still the same after the approval
@@ -230,7 +233,7 @@
             if @closed is 'self'
                 "/>"
             else if @closed
-                ">#{@content}</#{@name}>" # FIXME children ?
+                ">#{@content}</#{@name}>"
             else
                 ">#{@content}"

@@ -254,11 +257,13 @@
         @emit 'replaced', tag # internal
         tag

-    remove: () =>
-        @closed = 'removed' unless @closed
-        @emit 'remove', this
-        @emit 'removed' # internal
-        @removeAllListeners()
+    remove: (opts = {}) =>
+        @closed = 'removed' unless @closed or opts.soft
+        @emit 'remove', this, opts
+        @builder = null
+        @parent = null
+        @emit 'removed', opts.soft # internal
+        @removeAllListeners() unless opts.soft
         this

     ready: (callback) =>


add 300ms hugo htmlTab reload timeout

add 300ms hugo htmlTab reload timeout

--- lib/generators/hugo.coffee
+++ lib/generators/hugo.coffee
@@ -33,7 +33,7 @@
           if output.indexOf('Change detected') isnt -1
             setTimeout ()->
               atom.htmlTab.reload()
-            , 200
+            , 300
             # atom.nprogress.done()
       stderr: (err) ->
         console.error err


Style fixes for error badges #37

Style fixes for error badges #37

--- lib/go-outline-view.coffee
+++ lib/go-outline-view.coffee
@@ -19,7 +19,7 @@
     @div class: 'go-outline-tree-resizer tool-panel', 'data-show-on-right-side': atom.config.get('go-outline.showOnRightSide'), =>
       @nav class: 'go-outline-navbar', =>
         @div =>
-          @span class:"badge", '0/0', outlet: 'bdgErrors'
+          @span outlet: 'bdgErrors'
         @div class: "btn-group", =>
           @div class: "icon icon-mention", title: "show variables", outlet: 'btnShowVariables'
           @div class: "icon icon-gist-secret", title: "show private symbols", outlet: 'btnShowPrivate'
@@ -79,18 +79,15 @@
     isDone = (failedFiles.size + doneFiles.size) >= allFiles.size

     element = $(@bdgErrors)
-    element.text((doneFiles.size+failedFiles.size)+"/"+allFiles.size)
-    element.removeClass('badge-success badge-warning icon icon-stop icon-check icon-hourglass')
+    element.removeClass()
     if failedFiles.size > 0
-      if isDone
-        element.addClass('icon icon-stop')
-
-      element.addClass('icon icon-hourglass badge-warning')
-    else
-      if isDone
-        element.addClass('icon icon-check badge-success')
+      element.addClass('text-error')
+      element.text(failedFiles.size + ' error(s)')
+    else
+      if !isDone
+        element.text('processing...')
       else
-        element.addClass('icon icon-hourglass')
+        element.text('')

     @parserStatus.failedFiles = Array.from(failedFiles)
     @parserStatus.failedFiles.sort()
@@ -104,10 +101,6 @@
       end = "
"

       return start + @parserStatus.failedFiles.map((l) -> 'Parsing failed for '+l).join('
') + end
-    else
-      return "Parsing successful"
-
-

   initializeButtons: ->
     @showTests = atom.config.get('go-outline.showTests')


Remove call to EditorView::updateDisplay

This method is deprecated and a no-op

--- lib/go-to-line-view.coffee
+++ lib/go-to-line-view.coffee
@@ -35,7 +35,6 @@
     @detaching = true
     miniEditorFocused = @miniEditor.isFocused
     @miniEditor.setText('')
-    @miniEditor.updateDisplay()

     super


#1647 - Calculation question can't be used in skip logic condition

--- jsapp/xlform_model_view/mv.skipLogicHelpers.coffee
+++ jsapp/xlform_model_view/mv.skipLogicHelpers.coffee
@@ -332,7 +332,7 @@
       questions = []
       limit = false

-      non_selectable = ['datetime', 'time', 'note', 'calculate', 'group']
+      non_selectable = ['datetime', 'time', 'note', 'group']

       @survey.forEachRow (question) =>
         limit = limit || question is @current_question


Update to use Linter API for multiline

Update to use Linter API for multiline

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -72,14 +72,6 @@
   provideLinter: ->
     return unless atom.config.get 'haskell-ghc-mod.useLinter'
     backend = new IdeBackend @process
-    multiline = (message) ->
-      elem = document.createElement 'linter-multiline-message'
-      elem.tabIndex = 0
-      console.log message.split(/\n/).map((l) -> "<line>#{l}</line>").join("")
-      elem.innerHTML = message.split(/\n/).filter((l) -> l)
-        .map((l) -> "<line>#{l}</line>")
-        .join("")
-      elem
     [
       grammarScopes: ['source.haskell', 'text.tex.latex.haskell']
       scope: 'file' # or 'project'
@@ -91,12 +83,14 @@
               [message, messages...] = message.split /^(?!\s)/gm
               {
                 type: severity
-                html: multiline message
+                text: message
+                multiline: true
                 filePath: uri
                 range: [position, position.translate [0, 1]]
                 trace: messages.map (text) ->
                   type: 'trace'
-                  html: multiline text
+                  text: text
+                  multiline: true
               }
     ,
       grammarScopes: ['source.haskell']
@@ -108,10 +102,12 @@
             resolve res.map ({uri, position, message, severity}) ->
               [message, messages...] = message.split /^(?!\s)/gm
               type: severity
-              html: multiline message
+              text: message
+              multliline: true
               filePath: uri
               range: [position, position.translate [0, 1]]
               trace: messages.map (text) ->
                 type: 'trace'
-                html: multiline text
+                text: text
+                multiline: true
     ]


Ignore empty scopes and patterns

Ignore empty scopes and patterns

--- lib/wrap-guide-view.coffee
+++ lib/wrap-guide-view.coffee
@@ -29,13 +29,13 @@
     return @getDefaultColumn() unless Array.isArray(customColumns)
     for customColumn in customColumns when typeof customColumn is 'object'
       {pattern, scope, column} = customColumn
-      if pattern?
+      if pattern
         try
           regex = new RegExp(pattern)
         catch
           continue
         return parseInt(column) if regex.test(path)
-      else if scope?
+      else if scope
         return parseInt(column) if scope is scopeName
     @getDefaultColumn()


use singlesocket (buggy and issue with character encoding)

--- lib/models/terminal.coffee
+++ lib/models/terminal.coffee
@@ -16,28 +16,28 @@
     @setListeners()

   connect: () ->
-    @socket = new Websocket @ws_url
-
-    @socket.onopen = () =>
-      console.log('opened socket for terminal')
-      @emit 'terminal-session-opened'
-
-
-    @socket.onmessage = (msg) =>
-      console.log('message from singlesocket')
-      console.log(msg)
-      @emit 'terminal-message-received', msg.data
-
-    @socket.onclose = () =>
-      console.log('connection closed')
-      @emit 'terminal-session-closed'
-
-    @socket.onerror = (e) ->
-      console.log('error on terminal connection')
-      console.error(e)
+    @socket = new SingleSocket @ws_url,
+      onopen: () ->
+        console.log('opened socket for terminal')
+      onmessage: (msg) =>
+        console.log('message from singlesocket')
+        console.log(msg)
+        @emit 'terminal-message-received', msg
+      onclose: () ->
+        console.log('connection closed')
+      onerror: (e) ->
+        console.log('error on terminal connection')
+        console.error(e)

   send: (data) ->
+    console.log('sending:::', data)
     @socket.send(data)
+
+  # updateConnectionState: (state) ->
+    # if state == 'closed'
+      # this.emit 'terminal-session-closed'
+    # else
+      # this.emit 'terminal-session-opened'

   setListeners: () ->
     ipc.on 'request-terminal-view', (request) =>


Bind textInput listener w/ DOM API, not atom.commands

--- lib/view-models/vim-command-mode-input-view.coffee
+++ lib/view-models/vim-command-mode-input-view.coffee
@@ -1,4 +1,4 @@
-{View, TextEditorView} = require 'atom'
+{View} = require 'atom'

 module.exports =
 class VimCommandModeInputView extends View
@@ -29,7 +29,7 @@

   handleEvents: ->
     if @singleChar?
-      atom.commands.add(@editorElement, 'textInput', @autoSubmit)
+      @editorElement.addEventListener('textInput', @autoSubmit)
     else
       atom.commands.add(@editorElement, 'editor:newline', @confirm)
     atom.commands.add(@editorElement, 'core:confirm', @confirm)


Fix bug discovering params if path timestamp is 1

--- lib/db.coffee
+++ lib/db.coffee
@@ -264,7 +264,7 @@
         else
           for k, v of projTree
             p = path.concat(k)
-            res.push([p, +(obj['_timestamp'] ? 1)])
+            res.push([p, obj['_timestamp']]) if obj['_timestamp']
             loaded.push([p, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)])

     for k, v of device


Fix Helpers declaration.

Fix Helpers declaration.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -7,6 +7,7 @@
       url: '/api/subject-detail/:id/'
 ]

+
 svcs.factory 'Session', ['$resource', ($resource) ->
   $resource '/api/session-detail/:id/', null,
     detail:
@@ -14,7 +15,8 @@
       url: '/api/session-detail/:id/'
 ]

-svcs.factory 'Helpers', [->
+
+svcs.factory 'Helpers', ->
   # If the given attribute value is a string, then this function
   # resets it to the parsed date.
   fixDate: (obj, attr) ->
@@ -190,7 +192,6 @@
       d3.select(this).attr(k, v)
     # Reattach the text element to the anchor.
     t.appendTo(a)
-]

 svcs.factory 'Modeling', ['Helpers', (Helpers) ->


:bug: Use serial comma in enumeration if three or more terms

--- lib/package-updater.coffee
+++ lib/package-updater.coffee
@@ -100,6 +100,7 @@
         if index + 1 < items.length
           expression += ', '
         else
+          expression += ',' if items.length > 2
           expression += ' and '

       expression += item


Fix the expected error message in OS download test

--- tests/integration.coffee
+++ tests/integration.coffee
@@ -1207,4 +1207,4 @@

 					it 'should be rejected with an error message', ->
 						promise = resin.models.os.download('foo-bar-baz')
-						m.chai.expect(promise).to.be.rejectedWith('No such device type')
+						m.chai.expect(promise).to.be.rejectedWith('Request error: Device type "foo-bar-baz" not found')


Delete set_enable

Delete set_enable

--- src/api/host.coffee
+++ src/api/host.coffee
@@ -17,7 +17,6 @@
   for param, field of {
     'name_label'
     'name_description'
-    'enabled'
   }
     continue unless param of params

@@ -34,9 +33,6 @@
     optional: true
   name_description:
     type: 'string'
-    optional: true
-  enabled:
-    type: 'boolean'
     optional: true

 set.resolve = {


uncommenting integration test debugging

uncommenting integration test debugging

--- jsapp/test/unit/xlform/integration.tests.coffee
+++ jsapp/test/unit/xlform/integration.tests.coffee
@@ -38,7 +38,6 @@
         getName = (r)-> names.push r.get('name').get('value')
         @survey.forEachRow(getName, includeGroups: true)
         names
-      # @save = ()-> $('.test-div').removeClass('test-div').addClass('saved-test-div')
       @load_group_csv = ()=>
         @load_csv(
           survey: [
@@ -52,7 +51,7 @@
           ]
         )
     afterEach ->
-      # $('.test-div').remove()
+      $('.test-div').remove()

     describe 'represents required checkbox properly', ->
       beforeEach ->


tp #1403

tp #1403

cancelling top-row add re-hides the empty message

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -47,6 +47,7 @@
       @line.html $viewTemplates.$$render('xlfRowSelector.namer')
       $.scrollTo @line, 200, offset: -300
       $(window).on 'keydown.cancel_add_question',  (evt) =>
+        # user presses the escape key
         if evt.which == 27
           @hide()

@@ -83,7 +84,9 @@
       @line.empty().removeClass("expanded").css "height": 0
       $(window).off 'keydown.cancel_add_question'
       $('body').off 'mousedown.cancel_add_question'
-      @line.parents(".survey-editor__null-top-row").removeClass "expanded"
+      @line.parents(".survey-editor__null-top-row")
+          .removeClass("expanded")
+          .addClass("survey-editor__null-top-row--hidden")

     selectMenuItem: (evt)->
       @question_name = @line.find('input').val()


add check for active editor

add check for active editor

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -4,6 +4,7 @@
 CSON = require('CSON')

 module.exports = Atomizr =
+  atom: atom
   workspace: atom.workspace
   grammars: atom.grammars
   subscriptions: null
@@ -35,6 +36,9 @@
   # Automatic conversion, based on scope
   autoConvert: ->
     editor = @workspace.getActiveTextEditor()
+    if typeof editor is "undefined"
+      @atom.beep()
+      return
     scope = editor.getGrammar().scopeName

     if scope is "source.coffee"
@@ -45,6 +49,9 @@
   # Convert Atom snippet into Sublime Text completion
   atomToSubl: ->
     editor = @workspace.getActiveTextEditor()
+    if typeof editor is "undefined"
+      @atom.beep()
+      return
     text = editor.getText()

     obj = CSON.parseCSONString(text)
@@ -84,6 +91,9 @@
   # Convert Sublime Text completion into Atom snippet
   sublToAtom: ->
     editor = @workspace.getActiveTextEditor()
+    if typeof editor is "undefined"
+      @atom.beep()
+      return
     text = editor.getText()

     obj = CSON.parseJSONString(text)
@@ -122,6 +132,9 @@
   # Convert Atom snippet format (CSON to JSON, or vice versa)
   atomToAtom: ->
     editor = @workspace.getActiveTextEditor()
+    if typeof editor is "undefined"
+      @atom.beep()
+      return
     scope = editor.getGrammar().scopeName

     # Automatic conversion, based on scope
@@ -132,6 +145,9 @@

   csonToJson: ->
     editor = @workspace.getActiveTextEditor()
+    if typeof editor is "undefined"
+      @atom.beep()
+      return
     text = editor.getText()

     # Conversion
@@ -144,6 +160,9 @@

   jsonToCson: ->
     editor = @workspace.getActiveTextEditor()
+    if typeof editor is "undefined"
+      @atom.beep()
+      return
     text = editor.getText()

     # Conversion
@@ -153,4 +172,3 @@
     # Write back to editor and change scope
     editor.setText(output)
     editor.setGrammar(@grammars.grammarForScopeName('source.coffee'))
-


Ref: リファクタリング

Ref: リファクタリング

--- src/core/applyMod.coffee
+++ src/core/applyMod.coffee
@@ -49,7 +49,7 @@
 ###*
  * dataイベントから適応する
  *###
-_applyFromData = (outputFolder, {path: pa, fullPath}, pathList, cb) ->
+_applyFromData = (outputFolder, {path: pa, fullPath}, pathList) ->
   return new Promise( (resolve, reject) ->
     pathList.add(pa)
     fstream
@@ -69,7 +69,7 @@
 ###*
  * entryイベントから適応する
  *###
-_applyFromEntry = (outputFolder, entry, pathList, cb) ->
+_applyFromEntry = (outputFolder, entry, pathList) ->
   return new Promise( (resolve, reject) ->
     pathList.add(entry.path)
     entry
@@ -89,15 +89,19 @@
  * MODを適応します
  * @param {"add"|"delete"} type 適応するか解除するか
  * @param {string} mod "{repo: {type: repoType, name: repo}, name: name}"
- * @param {Function} callback 適応完了時に実行
+ * @param {Function} progress 進捗報告
  * @return {Promise}
  ###
-applyMod = (type, mod, callback) ->
+applyMod = (type, mod, progress) ->
   pathType = config.get("blitzPathType")
   if pathType is "folder"
     outputFolder = path.normalize(config.get("blitzPath"))
   else
     outputFolder = TEMP_FOLDER
+
+  log = (phase) ->
+    return progress(phase, type, mod)
+
   try
     pathList = new Set()
     await fs.ensureDir(outputFolder)
@@ -105,9 +109,6 @@
       when "add" then folder = "install"
       when "delete" then folder = "remove"
       else throw new Error("Unknown type")
-
-    log = (phase) ->
-      return callback(phase, type, mod)

     if mod.repo.type is "remote"
       stream = _getFromRemote(folder, mod, log)
@@ -155,8 +156,8 @@
     )

     if pathType is "file"
-      callback("tempdone", type, mod)
-      callback("zipcompress", type, mod)
+      log("tempdone")
+      log("zipcompress")
       blitzPath = path.normalize(config.get("blitzPath"))
       switch config.get("platform")
         when "a" then prefix = "assets"
@@ -175,7 +176,7 @@
               .generateNodeStream(streamFiles: true)
               .pipe(fs.createWriteStream(blitzPath))
               .on("finish", ->
-                callback("zipcompressed", type, mod)
+                log("zipcompressed")
                 resolve()
                 return
               )
@@ -187,11 +188,10 @@
     switch type
       when "add" then config.add("appliedMods", {repo: mod.repo.name, name: mod.name})
       when "delete" then config.remove("appliedMods", {repo: mod.repo.name, name: mod.name})
-    callback("done", type, mod)
-    fs.remove(TEMP_FOLDER)
+    log("done")
   catch err
-    fs.remove(TEMP_FOLDER)
-    callback("fail", type, mod, err)
+    progress("fail", type, mod, err)
+  fs.remove(TEMP_FOLDER)
   return

 ###*


fix potential cookie vs coffeescript syntax issue

--- lib/server.passport.coffee
+++ lib/server.passport.coffee
@@ -78,8 +78,7 @@
   userSession =
     token: req.sessionID
     user: user
-  resp.cookie('userSession', JSON.stringify(userSession), maxAge: 90000, secret: false)
-
+  resp.cookie('userSession', JSON.stringify(userSession), {maxAge: 900000, secret: false})
   resp.redirect("/slideshow/")

 slideshowRedirect = (service) ->


fix #94: fall back to string if json fails

--- src/builtins.coffee
+++ src/builtins.coffee
@@ -78,7 +78,11 @@

     fn 'object', 'Prints an object as JSON', (o) ->
       $pre = $ '<pre>'
-      s = JSON.stringify(o, null, '  ') or new String o
+      try
+        s = JSON.stringify(o, null, '  ')
+      catch
+        s = null
+      s ||= new String o
       format_code s, 'json', $pre
       @add_rendered $pre


Override rails jquery ujs

Override rails jquery ujs

--- vendor/assets/javascripts/ajaxify_rails.js.coffee
+++ vendor/assets/javascripts/ajaxify_rails.js.coffee
@@ -31,6 +31,38 @@

 set_content_container = (new_content_container) ->
   content_container = new_content_container
+
+
+# Override rails jquery_ujs on handling links with :data-method
+# Example:
+#   <%= link_to 'test', test_path, :method => :delete %>
+rails_ujs_fix = ->
+  return false if jQuery['rails'] == undefined
+
+  jQuery.rails.handleMethod = (link) ->
+    href = $.rails.href(link)
+    method = link.data('method')
+    target = link.attr('target')
+    csrf_token = $('meta[name=csrf-token]').attr('content')
+    csrf_param = $('meta[name=csrf-param]').attr('content')
+    form = $("<form method='post' action='#{href}'></form>")
+    metadata_input = "<input name='_method' value='#{method}' type='hidden' />"
+
+    # Let's not use ajaxify in form submission if link
+    # has `no_ajaxify` class.
+    form.addClass('no_ajaxify') if link.hasClass('no_ajaxify')
+
+    if csrf_param !== undefined && csrf_token !== undefined
+      metadata_input += '<input name="' +
+        csrf_param + '" value="' +
+        csrf_token + '" type="hidden" />'
+
+    form.attr('target', target) if target
+
+    form.hide().append(metadata_input).appendTo('body')
+    form.submit()
+
+

 init = (options = {}) ->
   base_paths = options.base_paths if 'base_paths' of options
@@ -40,6 +72,7 @@
   content_container = options.content_container if 'content_container' of options
   correct_url() unless $('meta[name="ajaxify:dont_correct_url"]').length > 0
   scroll_to_top = options.scroll_to_top if 'scroll_to_top' of options
+  rails_ujs_fix()

 ajaxify = ->


Rollback to jQuery's offset() for dropdown positionning

--- vendor/assets/javascripts/chosen/chosen.js.coffee
+++ vendor/assets/javascripts/chosen/chosen.js.coffee
@@ -349,16 +349,21 @@
   update_dropdown_position: ->
     return unless @opened

+    offsets = @$container.offset()
     rect = @$container[0].getBoundingClientRect()
     border_width = parseInt(@$container.css("border-bottom-width"))
-    width = if rect.width then rect.width else rect.right - rect.left
+
+    if rect.width
+      width = rect.width
+      height = rect.height
+    else
+      width = rect.right - rect.left
+      height = rect.bottom - rect.top

     @$dropdown.css
-      left: "#{rect.left}px",
-      top: "#{rect.bottom - border_width}px"
+      left: "#{offsets.left}px",
+      top: "#{offsets.top + height - border_width}px"
       width: "#{width}px"
-
-    return

   update_dropdown_content: ->
     @insert_suggestion()


:memo: Make doc more clear

:memo: Make doc more clear

--- lib/symbol-provider.coffee
+++ lib/symbol-provider.coffee
@@ -200,8 +200,13 @@
     matchedSymbols = []

     # Handle the case where a symbol is a variable in some cases and, say, a
-    # class in others. We want all symbols in the file to have the same type.
-    # e.g. `class` types are higher priority than `variables`
+    # class in others. We want all symbols of the same name to have the same type. e.g.
+    #
+    # ```coffee
+    # SomeModule = require 'some-module' # This line parses SomeModule as a var
+    # class MyClass extends SomeModule # This line parses SomeModule as a class
+    # ```
+    # `class` types are higher priority than `variables`
     cacheSymbolType = (word, currentType) =>
       word = getWordKey(word)
       return symbolTypes[word] = currentType unless symbolTypes[word]?


Replace depricated APIs

Replace depricated APIs

--- lib/transient-emacs.coffee
+++ lib/transient-emacs.coffee
@@ -48,7 +48,7 @@
           @isearch_word += e.Text
           @isearch @isearch_word

-    @keymap_listener = atom.keymap.onDidMatchBinding (e)=>
+    @keymap_listener = atom.keymaps.onDidMatchBinding (e)=>
       if @isearch_tile
         if e.keystrokes.length == 1
           @isearch_word += e.keystrokes
@@ -59,7 +59,7 @@
         else if e.keystrokes == 'space'
           @isearch_word += ' '
           @isearch @isearch_word
-    @keymap_flistener = atom.keymap.onDidFailToMatchBinding (e)->
+    @keymap_flistener = atom.keymaps.onDidFailToMatchBinding (e)->
       # console.log "Fail",e

     @killring = new KillRing()
@@ -76,7 +76,7 @@
     @killring.seal()

   cancel: ->
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     return if @deactivate_isearch()
     return if @clear_selections editor
     return if @consolidate_selections editor
@@ -97,11 +97,11 @@
       else
         @deactivate_isearch()
       return false
-    editorView = atom.views.getView atom.workspace.getActiveEditor()
+    editorView = atom.views.getView atom.workspace.getActiveTextEditor()
     atom.commands.dispatch editorView, 'core:backspace'

   activate_isearch: (forward)->
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     editorView = atom.views.getView editor
     $(editorView).addClass "searching"
     @isforward = forward
@@ -114,7 +114,7 @@
       @isearch_tile = null
       @isearch_last = @isearch_word
       @isearch_word = ""
-      editor = atom.workspace.getActiveEditor()
+      editor = atom.workspace.getActiveTextEditor()
       editorView = atom.views.getView editor
       $(editorView).removeClass "searching"
       return true
@@ -148,7 +148,7 @@
   isearch: (word)->
     @_update_statusbar word,true
     return unless word
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     return if not editor or editor.mini
     buffer_end = editor.getBuffer().getEndPosition()
     re = @_create_re word
@@ -179,20 +179,20 @@
     false

   set_mark: ->
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     editorView = atom.views.getView editor
     $(editorView).toggleClass "transient-marked"
     cursor.clearSelection() for cursor in editor.getCursors()

   kill_region_or_backward_word: ->
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     if editor.getSelection().isEmpty()
       @kill_backward_word()
     else
       @kill_region()

   _push_regeon_to_killring: ->
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     editorView = atom.views.getView editor
     $(editorView).removeClass "transient-marked"
     texts = (s.getText() for s in editor.getSelections())
@@ -210,7 +210,7 @@

   kill_backward_word: ->
     @is_user_command = false
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     editor.selectToBeginningOfWord()
     texts = (s.getText() for s in editor.getSelections())
     @killring.put texts,false
@@ -219,7 +219,7 @@

   kill: ->
     @is_user_command = false
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     editor.selectToEndOfLine()
     texts = (s.getText() or '\n' for s in editor.getSelections())
     @killring.put texts,true
@@ -227,7 +227,7 @@
     @is_user_command = true

   yank: ->
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     cursors = editor.getCursors()
     top = @killring.top()
     if cursors.length == top?.length


Fixes to https->https bridge, discarded idea of http->https bridge whatsoever.

--- logic.coffee
+++ logic.coffee
@@ -31,7 +31,6 @@
       proxyTimeout: 5 * 60 * 1000,
       secure: false,
       autoRewrite: true,
-      protocolRewrite: 'http',
       forceRewrite: true
     }

@@ -300,7 +299,7 @@
     response.write "Not found"
     response.end()

-  tryDispatchInRoute: (request, response, route) ->
+  dispatchInRoute: (request, response, route) ->
     uthis = this

     if route.log?
@@ -339,9 +338,7 @@
       response.statusCode = route.response.code
       response.write route.response.body
       response.end()
-      return true
-
-    if route.forward?
+    else if route.forward?
       protocol = "http"
       if route.forward.ssl
         protocol = "https"
@@ -349,23 +346,20 @@
       target = "#{protocol}://#{route.forward.host}:#{route.forward.port}"
       options = {
         target: target,
-        secure: false,
-        ssl: {}
+        secure: false
       }
+      @printCallback target
+
+      proxyNow = () ->
+        uthis.printCallback "Forwarding to #{target}"
+        uthis.proxy.web request, response, options

       if route.delay?
         @printCallback "Delaying by #{route.delay} seconds"

-        cbk = () ->
-          uthis.proxy.web request, response, options
-
-        @setTimeoutCallback cbk, (route.delay) * 1000
+        @setTimeoutCallback proxyNow, (route.delay) * 1000
       else
-        @proxy.web request, response, options
-
-      return true
-
-    return false
+        proxyNow()

   dispatch: (request, response) ->
     path = request.url.substring(1)
@@ -374,14 +368,11 @@
     matchedRoutes = this.findMatchingRoutes {path: path, \
                                              method: request.method}
     if matchedRoutes.length > 0
-      @printCallback "Matched #{matchedRoutes.length} routes"
+      @printCallback "Matched #{matchedRoutes.length} routes, will dispatch in first one"
       for route in matchedRoutes
         @printCallback "In route #{route.key}"
         this.printRoute route
-
-        shouldStop = this.tryDispatchInRoute(request, response, route)
-        if shouldStop
-          return null
+        return this.dispatchInRoute(request, response, route)

     @printCallback "No idea how to process the request!"
     this.dispatchNoRoutes request, response


Remove unused dependency

Remove unused dependency

--- lib/tasks/actions/build_to_speech_action.coffee
+++ lib/tasks/actions/build_to_speech_action.coffee
@@ -10,7 +10,6 @@

   initialize: ->
     super()
-    @say = @app.getPlugin('say')
     @tts = new GoogleTranslatorTtsStrategy @tempFileHolder

   run: (scope) ->


Don't crash when handling errors

Don't crash when handling errors

--- lib/views/repl-view.coffee
+++ lib/views/repl-view.coffee
@@ -1,6 +1,7 @@
 Cycle = require '@cycle/core'
 CycleDOM = require '@cycle/dom'
 highlighter = require '../utils/highlighter'
+Rx = require 'rx-lite'

 fontOptions = () ->
   fontSize = atom.config.get 'language-idris.panelFontSize'
@@ -22,21 +23,41 @@
   '-webkit-font-feature-settings': webkitFontFeatureSettings
   'font-family': fontFamily

+styles = fontOptions()
+
+# highlight : forall a.
+#   { code : String, highlightInformation : HighlightInformation } ->
+#   CycleDOM
+highlight = ({ code, highlightInformation }) ->
+  highlights = highlighter.highlight code, highlightInformation
+  highlighter.highlightToCycle highlights
+
+displaySuccess = (line) ->
+  highlightedCode = highlight line
+
+  CycleDOM.h 'pre',
+    {
+      className: 'idris-repl-output'
+      style: styles
+    },
+    [
+      highlightedCode
+    ]
+
+displayError = (line) ->
+  CycleDOM.h 'pre', { }, line.message
+
 REPLCycle =
-  # highlight : forall a.
-  #   { code : String, highlightInformation : HighlightInformation } ->
-  #   CycleDOM
-  highlight: ({ code, highlightInformation }) ->
-    highlights = highlighter.highlight code, highlightInformation
-    highlighter.highlightToCycle highlights
-
   # view : Observable State -> Observable CycleDOM
   view: (state$) ->
-    styles = fontOptions()
-
     state$.map (lines) ->
       lines = lines.map (line) ->
-        highlightedCode = REPLCycle.highlight line
+        answer =
+          if line.type == 'success'
+            displaySuccess line
+          else
+            displayError line
+
         CycleDOM.h 'div',
           {
             className: 'idris-repl-line'
@@ -48,14 +69,7 @@
                 CycleDOM.h 'span', { className: 'idris-repl-input-prompt' }, '> '
                 line.input
               ]
-            CycleDOM.h 'pre',
-              {
-                className: 'idris-repl-output'
-                style: styles
-              },
-              [
-                highlightedCode
-              ]
+            answer
           ]

       CycleDOM.h 'div',
@@ -89,9 +103,17 @@
             escapedLine = line.replace(/"/g, '\\"')
             options.model.interpret escapedLine
               .map (e) ->
+                type: 'success'
                 input: line
                 code: e.msg[0]
                 highlightInformation: e.msg[1]
+              .catch (e) ->
+                error =
+                  input: line
+                  type: 'error'
+                  message: e.message
+                  warnings: e.warnings
+                Rx.Observable.just error
           .scan ((acc, x) -> [x].concat acc), []
           .startWith []


Fix lmo href link function

Fix lmo href link function

--- lineman/app/components/lmo_href/lmo_href.coffee
+++ lineman/app/components/lmo_href/lmo_href.coffee
@@ -4,7 +4,7 @@
     path: '@lmoHref'
     model: '=lmoHrefModel'
   link: (scope, elem, attrs) ->
-    LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
+    route = LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
     elem.attr 'href', ''
     elem.bind 'click', ($event) ->
       if $event.metaKey


User for instead of _.each

User for instead of _.each

--- src/commands.coffee
+++ src/commands.coffee
@@ -13,7 +13,7 @@

   list: =>
     @_list (pullRequests) ->
-      _.each pullRequests, (pr, i) =>
+      for pr, i in pullRequests
         console.log "#{i + 1}: [#{pr.head.repo.name} @#{pr.user.login}]\t#{pr.title}"

   size: =>
@@ -26,12 +26,12 @@
   ping: (index) =>
     @_list (pullRequests) =>
       if @program.all
-        _.each pullRequests, (pr) =>
+        for pr, i in pullRequests
           @_postComment pr
       else if index?
         @_postComment pullRequests[index - 1]
       else
-        _.each pullRequests, (pr, i) =>
+        for pr, i in pullRequests
           console.log "#{i + 1}: [#{pr.head.repo.name} @#{pr.user.login}]\t#{pr.title}"

         console.log ""
@@ -47,7 +47,7 @@
       else if index?
         open pullRequests[index - 1].html_url
       else
-        _.each pullRequests, (pr, i) =>
+        for pr, i in pullRequests
           console.log "#{i + 1}: [#{pr.head.repo.name} @#{pr.user.login}]\t#{pr.title}"

         console.log ""


Also search PATH when looking for goimports/golint/oracle.

If the binary is also found in some GOPATH/bin directory then the GOPATH-version has priority.

--- lib/go.coffee
+++ lib/go.coffee
@@ -76,21 +76,29 @@
     return result

   goimports: ->
-    return @gopathBinItem('goimports')
+    return @gopathOrPathBinItem('goimports')

   golint: ->
-    return @gopathBinItem('golint')
+    return @gopathOrPathBinItem('golint')

   oracle: ->
-    return @gopathBinItem('oracle')
+    return @gopathOrPathBinItem('oracle')

-  gopathBinItem: (name) ->
+  gopathOrPathBinItem: (name) ->
+    pathresult = false
+    # first look in PATH for the binaries
+    if @env.PATH?
+      elements = @env.PATH.split(path.delimiter)
+      for element in elements
+        target = path.resolve(path.normalize(path.join(element, name + @exe)))
+        pathresult = target if fs.existsSync(target)
+
     gopaths = @splitgopath()
-    return false unless gopaths? and _.size(gopaths) > 0
+    return pathresult unless gopaths? and _.size(gopaths) > 0
     for item in gopaths
       result = path.resolve(path.normalize(path.join(item, 'bin', name + @exe)))
       return result if fs.existsSync(result)
-    return false
+    return pathresult

   toolsAreMissing: ->
     return true if @format() is false


Improve nodes unloading

Improve nodes unloading

--- src/vtree_src/dom.coffee
+++ src/vtree_src/dom.coffee
@@ -1,6 +1,7 @@
 class DOM

   @html: ($el, html) ->
+    $el.children().each (i, child) -> $(child).remove()
     $el.html(html)
     $el.trigger('refresh')

@@ -27,6 +28,7 @@
     AsyncFn.addToCallQueue ->
       dfd = new $.Deferred()
       AsyncFn.setImmediate ->
+        $el.children().each (i, child) -> $(child).remove()
         $el.html(html)
         $el.trigger('refresh')
         dfd.resolve()


Show format errors instead of crashing. Fix #283

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -457,10 +457,8 @@
     else
       out

-formatEither = (isTitle, format, text, leaveTeX = false) ->
-  return text unless text?
-
-  ## Search highlighting
+## Search highlighting
+formatSearch = (isTitle, text) ->
   if Meteor.isClient and Router.current()?.route?.getName() == 'search' and
      (search = Router.current()?.params?.search)?
     recurse = (query) ->
@@ -474,6 +472,11 @@
           recurse part
       null
     recurse parseSearch search
+  text
+
+formatEither = (isTitle, format, text, leaveTeX = false) ->
+  return text unless text?
+  text = formatSearch isTitle, text

   ## LaTeX and Markdown formats are special because they do their own math
   ## preprocessing at a specific time during its formatting.  Other formats
@@ -501,11 +504,26 @@
   text = postprocessAtMentions text
   sanitize text

+formatEitherSafe = (isTitle, format, text, leaveTeX = false) ->
+  try
+    formatEither isTitle, format, text, leaveTeX
+  catch e
+    if isTitle
+      """
+        Formatting error (bug in Coauthor)
+        #{_.escape text}
+      """
+    else
+      """
+
Formatting error (bug in Coauthor): #{e.toString()}

+
#{_.escape text}

+      """
+
 @formatBody = (format, body, leaveTeX = false) ->
-  formatEither false, format, body, leaveTeX
+  formatEitherSafe false, format, body, leaveTeX

 @formatTitle = (format, title, leaveTeX = false) ->
-  formatEither true, format, title, leaveTeX
+  formatEitherSafe true, format, title, leaveTeX

 @formatBadFile = (fileId) ->
   """<unknown file with ID #{fileId}>"""


Avoid breaking HTML comments in math punctuation processing (fix #535)

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -729,8 +729,16 @@
     weights = [boldWeight]
   else
     weights = [mediumWeight]
-  text.replace /MATH(\d+)ENDMATH([,.!?:;'"\-)\]}]*)|<span([^<>]*)>|<b\b|<strong\b|<\/\s*(b|strong|span)\s*>/g, (match, id, punct, spanArgs) ->
-    ## Detect math within bold mode
+  text.replace ///
+    MATH(\d+)ENDMATH
+    ([,.!?:;'"\-)\]}]*) # puncutation to pull into math mode
+    (?! -?> ) # prevent accidentally grabbing some of --> (end of HTML comment)
+    | ## Detect math within bold mode:
+    <span([^<>]*)> |
+    <b\b |
+    <strong\b |
+    <\/\s*(b|strong|span)\s*>
+  ///g, (match, id, punct, spanArgs) ->
     unless id?
       if spanArgs?
         spanArgs = /style\s*=\s*['"]font-weight:\s*(\d+)/i.exec spanArgs


minor: linter fix

minor: linter fix

--- workers/social/lib/social/models/teamtoken.coffee
+++ workers/social/lib/social/models/teamtoken.coffee
@@ -114,7 +114,7 @@

       emails.forEach (email) =>
         queue.push =>
-          @create client, {email}, (err, invitation) ->
+          @create client, { email }, (err, invitation) ->
             return queue.fin()  if err

             properties =
@@ -122,7 +122,7 @@
               invitee  : invitation.email
               link     : "#{protocol}//#{hostname}/Teams/#{encodeURIComponent invitation.code}"

-            Tracker.identifyAndTrack invitation.email, { subject : Tracker.types.CREATE_TEAM }, properties
+            Tracker.identifyAndTrack invitation.email, { subject: Tracker.types.CREATE_TEAM }, properties
             queue.fin()

       dash queue, callback


Notifying trait: Remove duplicate JMailNotification.create to prevent multiple e-mails about same content.

--- workers/social/lib/social/traits/notifying.coffee
+++ workers/social/lib/social/traits/notifying.coffee
@@ -35,10 +35,6 @@
         JMailNotification.create {actor, receiver, event, contents}, (err)->
           console.error err if err

-      if receiver instanceof JAccount and receiver.type isnt 'unregistered'
-        JMailNotification.create {actor, receiver, event, contents}, (err)->
-          console.error err if err
-
     if actor? and not receiver.getId().equals actor.id
       receiver?.sendNotification? event, contents

@@ -46,7 +42,8 @@
       # do not send mail notification for koding group
       # return if koding
       subject = contents.subject
-      return  if subject and subject.constructorName is 'JGroup' and subject.slug is "koding"
+      return  if subject and subject.constructorName is 'JGroup' \
+                         and subject.slug is "koding"

       do createActivity
       do sendNotification


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -21,7 +21,7 @@
       }

     if value?
-      # Use the value given
+      # Use the value given if any
       I[name] = options.value
     else
       # Initialize field if it doesn't exist


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -32,7 +32,7 @@
     # Turbo Cooldown
     player = GameObject()

-    # turboTimer
+    # turboTimer starts at 1000
     player.cooldown "turboTimer",
       approachBy: 5
       value: 1000


Improved task system.

Improved task system.

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -62,6 +62,9 @@
 		@steps = []
 		@recoveries = []

+	next: (task, callback) ->
+		@steps.unshift [task, callback]
+
 	step: (task, callback) ->
 		@steps.push [task, callback]

@@ -69,48 +72,50 @@
 		@recoveries.push [task, callback]

 	ignite: (dry, callback) ->
-		if dry
-			for entry in @steps
-				task = entry[0]
-				task_callback = entry[1]
-
+		exec_err = null
+		exec_pointer = 'steps'
+
+		test = () =>
+			return true if @[exec_pointer].length > 0
+			return false if exec_pointer == 'recoveries'
+
+			exec_pointer = 'recoveries'
+
+			return do arguments.callee
+
+		fn = (callback) =>
+			entry = @[exec_pointer].shift()
+			task = entry[0]
+			task_callback = entry[1]
+
+			if dry
 				logsmight.info task.desc if task.desc?

-			for entry in @recoveries
-				task = entry[0]
-				task_callback = entry[1]
-
-				logsmith.info task.desc if task.desc?
-
-			do callback
-		else
-			steps_queue = async.queue (task, callback) ->
+				return callback null if callback
+			else
+				super_callback = (err) =>
+					task_callback err if task_callback
+
+					if err
+						if exec_pointer == 'recoveries'
+							logsmith.error err
+						else
+							@steps = []
+							exec_err = err
+							exec_pointer = 'recoveries'
+
+					return callback null if callback
+
 				logsmight.info task.desc if task.desc?

-				return task.run callback if task.run?
-				return task callback if task
+				return task.run super_callback if task.run?
+				return task super_callback if task

-			steps_queue.drain = () =>
-				recoveries_queue = async.queue (task, callback) ->
-					logsmight.info task.desc if task.desc?
-
-					return task.run callback if task.run?
-					return task callback if task
-
-				recoveries_queue.drain = callback
-
-				for entry in @recoveries
-					task = entry[0]
-					task_callback = entry[1]
-
-					recoveries_queue.push task, task_callback
-
-			for entry in @steps
-				task = entry[0]
-				task_callback = entry[1]
-
-				steps_queue.push task, task_callback
-
+		async.whilst test, fn, (err) ->
+			return callback err if err
+			return callback exec_err if exec_err
+			return callback null if callback
+
 	exec: () -> throw new Error "not implemented"
 	spawn: () -> throw new Error "not implemented"


:bug: Fix a bug where we're iterating over a string, not an array

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -61,7 +61,7 @@
       regex = xcache.get(rawRegex)
     else
       xcache.set(rawRegex, regex = XRegExp(rawRegex))
-    for line in data
+    for line in data.split(/\r?\n/g)
       match = XRegExp.exec(line, regex)
       if match
         options.baseReduction = 1 unless options.baseReduction


adding in exercise cache so that it's easier to look up exercises by id

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -10,11 +10,17 @@

   reset: ->
     @_exercises = []
+    @_exerciseCache = []

   load: (courseId, pageIds) ->
-
+
   loaded: (obj, courseId, pageIds) ->
     @_exercises[pageIds.toString()] = obj.items
+    _exerciseCache = []
+    _.each obj.items, (exercise) ->
+      _exerciseCache[exercise.id] = exercise
+
+    @_exerciseCache = _exerciseCache
     @emitChange()

   exports:
@@ -24,6 +30,8 @@
       @_exercises[pageIds.toString()] or throw new Error('BUG: Invalid page ids')
     getQuestion: (exercise) ->
       JSON.parse(exercies.content)
+    getExerciseById: (exercise_id) ->
+      @_exerciseCache[exercise_id]

 {actions, store} = makeSimpleStore(ExerciseConfig)
 module.exports = {ExerciseActions:actions, ExerciseStore:store}


auto open new document

auto open new document

--- demo/client/client.coffee
+++ demo/client/client.coffee
@@ -7,6 +7,9 @@
   "click button": ->
     Documents.insert
       title: "untitled"
+    , (err, id) ->
+        return unless id
+        Session.set("document", id)

 Template.docItem.current = ->
   @_id is Session.get("document")


bugfix non-disappearing save button in course view

--- app/views/courses/update.coffee
+++ app/views/courses/update.coffee
@@ -13,6 +13,7 @@
 $('#course_short_title').addClass('is-invalid')
 <% end %>
 <% else %>
+$('#course-basics-save').hide()
 $('#course-basics-warning').hide()
 $('#course-basics-cancel').hide()
 <% end %>


Inspecting functions handy for generating of JSON Schema enums.

--- lib/inspect.coffee
+++ lib/inspect.coffee
@@ -1,4 +1,8 @@
 # Utility functions for inspecting the MSON AST tree
+
+
+# Listing of all primitive types as defined in MSON AST spec.
+primitiveTypes = ['boolean', 'string', 'number']

 # Turns top-level *Named Type* into an *Element* carrying a *Value Member*.
@@ -33,6 +37,11 @@
   else
     filter = (val) -> true
   (elementNode.content.valueDefinition?.values or []).filter filter
+
+
+# Lists all defined variable values.
+listVariableValues = (elementNode) ->
+  (val for val in (elementNode.content.valueDefinition?.values or []) when val.variable)

 # Takes *Element* node and lists its attributes, such as `required`, `fixed`, etc.
@@ -73,6 +82,19 @@
   listNestedElements arrayElementNode, ['value']

+# Convenience function.
+hasVariableValues = (elementNode) ->
+  !!listVariableValues(elementNode).length
+
+
+# Convenience function.
+haveVariableValues = (elementNodes) ->
+  for elementNode in elementNodes
+    if hasVariableValues elementNode
+      return true
+  false
+
+
 # Takes type node and finds out whether it has more than one value
 # in value definition.
 hasMultipleValues = (elementNode) ->
@@ -85,16 +107,33 @@
   (section for section in (elementNode.content?.sections or []) when section.class is 'memberType').length

-# Lists possible heritage objects {fixed, typeName} which can be applied to
+# Returns true in case all given type names are the same and at the same time
+# they're also primitive.
+areIdenticalAndPrimitive = (typeNames) ->
+  first = typeNames[0]
+  return false unless first in primitiveTypes
+  for typeName in typeNames
+    return false if typeName isnt first
+  true
+
+
+# Lists possible 'heritage objects' which can be applied to
 # sub-members of given parent node. In most cases, the resulting array
 # will contain just one item, but for some MSON constructs, such as
 # `array[number,string]`, multiple options will be returned.
 #
 # The *resolvedType* argument is optional as nested types are relevant only
 # for arrays and enums.
+#
+# Heritage object explanation:
+#
+# - fixed (boolean) - whether `fixed` flag is inherited
+# - parentTypeName (string) - type name of the parent container
+# - typeName (string) - inherited nested type name (for arrays and enums only)
 listPossibleHeritages = (fixed, resolvedType) ->
-  return [{fixed}] unless resolvedType?.nested?.length
-  ({fixed, typeName} for typeName in resolvedType.nested)
+  parentTypeName = resolvedType?.name or null  # intentionally not using `undefined` as "valid value"
+  nested = if resolvedType?.nested?.length then resolvedType.nested else [null]
+  {fixed, typeName, parentTypeName} for typeName in nested

 # Convenience function.
@@ -111,11 +150,15 @@
   listProperties
   listItems
   listValues
+  listVariableValues
   isRequired
   isFixed
   isOrInheritsFixed
+  hasVariableValues
+  haveVariableValues
   hasMultipleValues
   hasAnyMemberSections
+  areIdenticalAndPrimitive
   listPossibleHeritages
   getHeritage
 }


Improve ambari server discovery

Improve ambari server discovery

--- actions/ambari_client.coffee
+++ actions/ambari_client.coffee
@@ -14,7 +14,7 @@
 ----
 ###
 module.exports.push name: 'Ambari Client # Wait', timeout: -1, callback: (ctx, next) ->
-    server = ctx.hosts_with_module 'histi/actions/ambari_server', 1
+    server = ctx.host_with_module 'histi/actions/ambari_server', true
     {name} = ctx.config.ambari
     ready = ->
       request


Refactored ngMatrix code to be more readable.

--- src/app/ng-matrix/ngMatrix.coffee
+++ src/app/ng-matrix/ngMatrix.coffee
@@ -9,41 +9,51 @@
             colDefs = scope[attrs.colDefs]
             rowData = scope[attrs.rowData]

-            headerRow = angular.element '

'
-            hasSubDomain = false
-
             if colDefs
-                headerRow.append "	"
-                for col, def of colDefs
-                    headerRow.append "#{def.title}	"
-
-                element.append headerRow
+                headerRow = _createHeaderRow(colDefs)

             for key, row of rowData
                 matrixRow = angular.element "

"
-                domainSize = _.size(angular.copy(row))
-                if domainSize > 0
-                    hasSubDomain = (_.size(angular.copy(row[_.keys(row)[0]])) > 0)
-                    debugger
-                    if hasSubDomain
-                        matrixRow.append "#{key}	"
-                        element.append matrixRow
-
-                        for innerKey, value of row
-                            domainRow = angular.element "

"
-                            domainRow.append "#{innerKey}	"
-
-                            for subKey, subValue of value
-                                domainRow.append "#{subValue}	"
-
-                            element.append domainRow
-                    else
-                        matrixRow.append "#{key}	"
-                        for innerKey, value of row
-                            matrixRow.append "#{value}	" if (colDefs and colDefs[innerKey]) or not colDefs
-
-                        element.append matrixRow
+                hasSubDomain = _hasSubDomain(row)
+                if hasSubDomain
+                    _createMatrixRowsWithSubDomain(matrixRow, key, row)
+                else
+                    _createMatrixRow(matrixRow, key, row, colDefs)

             headerRow.prepend "	" if hasSubDomain

+        _createHeaderRow = (colDefs) ->
+            headerRow = angular.element '

'
+            headerRow.append "	"
+            for col, def of colDefs
+                headerRow.append "#{def.title}	"
+
+            element.append headerRow
+
+            return headerRow
+
+        _hasSubDomain = (row) ->
+            return (_.size(angular.copy(row[_.keys(row)[0]])) > 0)
+
+        _createMatrixRowsWithSubDomain = (matrixRow, key, row) ->
+            domainSize = _.size(angular.copy(row))
+            matrixRow.append "#{key}	"
+            element.append matrixRow
+
+            for innerKey, value of row
+                domainRow = angular.element "

"
+                domainRow.append "#{innerKey}	"
+
+                for subKey, subValue of value
+                    domainRow.append "#{subValue}	"
+
+                element.append domainRow
+
+        _createMatrixRow = (matrixRow, key, row, colDefs) ->
+            matrixRow.append "#{key}	"
+            for innerKey, value of row
+                matrixRow.append "#{value}	" if (colDefs and colDefs[innerKey]) or not colDefs
+
+            element.append matrixRow
+
         loadData()


Uses Turbolinks directly to perform page refresh after beehive reset. Fixes #976.

--- app/assets/javascripts/backend/beehive.js.coffee
+++ app/assets/javascripts/backend/beehive.js.coffee
@@ -26,7 +26,7 @@
         url: beehive.data("beehive-reset-url")
         type: 'post'
         success: (data, status, request) ->
-          document.location.reload(true)
+          Turbolinks.visit()

     setSortable: (beehive) ->
       $.beehive.addCellProposers(beehive)


Get restartIntervall too

Get restartIntervall too

--- node_modules_koding/processes/src/processes.coffee
+++ node_modules_koding/processes/src/processes.coffee
@@ -96,7 +96,7 @@
     return @child

   kontroldProcess: (options) ->
-    {name,onExit,restart,verbose} = options
+    {name,onExit,restart,restartInterval,verbose} = options

     console.log "[PROCESSES][#{@name}] force mode enabled. Trying to stop and kill all other #{name} workers" if verbose and options.kontrol.forceStart

@@ -110,8 +110,6 @@
       port               : options.kontrol.port
     }

-    # Disable restart for kontrold processes
-    # options.restart = no

     workerProcess.connect()
     console.log "[PROCESSES][#{name}] connected to Kontrold. Waiting for instructions..." if verbose
@@ -282,6 +280,13 @@
         console.log "[PROCESSES][#{name}]it's time you die process:#{name}" if verbose
         @kill name
       ,die.after
+
+    @child.on 'message',(msg) =>
+      onMessage msg if "function" is typeof onMessage
+      if msg['process-monitor']
+        @emit "monitorData", msg
+      else if options.kontrol.enabled and options.kontrol.nodeProcess
+        @emit "childData", msg

     # kontrold uses his own onExit and onClose
     unless options.kontrol?.enabled
@@ -299,12 +304,6 @@
             @run options
           ,restartInterval
           console.log "[PROCESSES][#{name}] restarting the process in #{restartInterval} msecs." if verbose
-      @child.on 'message',(msg) =>
-        onMessage msg if "function" is typeof onMessage
-        if msg['process-monitor']
-          @emit "monitorData", msg
-        else if options.kontrol.enabled and options.kontrol.nodeProcess
-          @emit "childData", msg

     # console.log @list
     return childObj


test(engine): fix mutation of provess.env in utils.os

--- packages/engine/test/utils/os.coffee
+++ packages/engine/test/utils/os.coffee
@@ -7,27 +7,27 @@
   describe 'whoami', ->

     it 'for ssh', ->
-      env = merge process.env
       ssh = {config: {username: 'ssh_username'}} # Fake SSH connection
       whoami(ssh: ssh, platform: 'linux').should.eql 'ssh_username'
-      process.env = env

     it 'for windows', ->
-      env = merge process.env
+      {USERPROFILE} = process.env
       process.env['USERPROFILE'] = 'C:\\Users\\Zin_user'
       whoami(platform: 'win32').should.eql 'Zin_user'
-      process.env = env
+      process.env['USERPROFILE'] = USERPROFILE

     it 'for linux root', ->
-      env = merge process.env
+      {USER, HOME} = process.env
       delete process.env['USER'] # Found this on Docker environment
       process.env['HOME'] = '/root'
       whoami(platform: 'linux').should.eql 'root'
-      process.env = env
+      process.env['USER'] = USER
+      process.env['HOME'] = HOME

     it 'for linux user', ->
-      env = merge process.env
+      {USER, HOME} = process.env
       delete process.env['USER'] # Found this on Docker environment
       process.env['HOME'] = '/home/linux_username'
       whoami(platform: 'linux').should.eql 'linux_username'
-      process.env = env
+      process.env['USER'] = USER
+      process.env['HOME'] = HOME


Select h1 also.

Select h1 also.

--- website/index.coffee
+++ website/index.coffee
@@ -14,4 +14,4 @@
   el = document.getElementById(id)
   $("*.selected").removeClass("selected")
   $("a[href='##{id}']").addClass("selected")
-  $(el).next("h2").addClass("selected")
+  $(el).next("h1, h2").addClass("selected")


Add sleep utils method

Add sleep utils method

--- app/assets/javascripts/globals/_functions.coffee
+++ app/assets/javascripts/globals/_functions.coffee
@@ -11,4 +11,8 @@
   $('.anchor').on 'click', ->
     $(window).scrollTo $(this).attr('href'), 'slow', offset: { top: -10 }
     false
-  return+  return
+
+@sleep = (ms) ->
+  start = new Date().getTime()
+  continue while new Date().getTime() - start < ms


EDSC-5: Focus keywords field on landing page

--- app/assets/javascripts/modules/landing.js.coffee
+++ app/assets/javascripts/modules/landing.js.coffee
@@ -9,6 +9,7 @@
     $('.landing-visible').toggle(isLandingPage)
     if (isLandingPage)
       $('.landing-dialog-toolbar').append($content)
+      $('#keywords').focus()
     else
       $('.landing-toolbar-container').append($content)
     $content.css(top: 0, left: 0, position: 'relative')


improve performance of auction results header

--- apps/artist/client/index.coffee
+++ apps/artist/client/index.coffee
@@ -17,6 +17,7 @@
 module.exports.ArtistView = class ArtistView extends Backbone.View

   initialize: (options) ->
+    @setupAuctionResults()
     @setupCurrentUser()
     @setupFollowButton()
     @setupArtworks()
@@ -25,7 +26,6 @@
     @setupRelatedPosts()
     @setupRelatedGenes()
     @setupShareButtons()
-    @setupAuctionResults()

   setupAuctionResults: ->
     auctionLots = new AuctionLots([], id: @model.get('id') )


extend PollingService to support update events

--- app/assets/javascripts/polling_service.js.coffee
+++ app/assets/javascripts/polling_service.js.coffee
@@ -3,6 +3,34 @@
   selector = null
   interval = null

+  updateElement= (element) ->
+    $element = $(element)
+    url = $element.data('updatePath')
+    return unless url
+
+    $.ajax
+      url: url,
+      dataType: 'html',
+      data: {'polling_service': true},
+      success: ( data, textStatus, jqXHR ) ->
+        console.log 'success'
+        # try to get loacation from response header
+        redirectTo = jqXHR.getResponseHeader('Location')
+        # response is a redirect
+        if redirectTo
+          # redirect url is equal to auth path
+          if redirectTo.indexOf('/auth/login/')>-1
+            # just reload to avoid redirect to a no layout page after login
+            window.location.reload()
+          else
+            # redirect to the redirectTo url
+            window.location = redirectTo
+        else
+          # no redirect -> replace content with html from response
+          #exists = $.contains(document.documentElement, $element)
+          $element.replaceWith(data)
+
+
   # update method which is called periodically
   update= () ->
     # get current timestamp
@@ -11,12 +39,6 @@
     # for each element found by selector do
     $(selector).each () ->
       $element  = $(this)
-
-      # data-updateUrl is set by server
-      url       = $element.data('updatePath')
-
-      # return if no update url defined
-      return this unless url

       # element's own update interval
       updateInterval = $element.data('updateInterval') || 10
@@ -24,29 +46,9 @@

       # modulo operation: rest of current timestamp divided by element's interval should be zero
       shouldUpdate = (timestamp % Math.round(updateInterval/ interval))==0
-
-      if shouldUpdate
-        # update content
-        $.ajax
-          url: url,
-          dataType: 'html',
-          data: {'polling_service': true},
-          success: ( data, textStatus, jqXHR ) ->
-            # try to get loacation from response header
-            redirectTo = jqXHR.getResponseHeader('Location')
-            # response is a redirect
-            if redirectTo
-              # redirect url is equal to auth path
-              if redirectTo.indexOf('/auth/login/')>-1
-                # just reload to avoid redirect to a no layout page after login
-                window.location.reload()
-              else
-                # redirect to the redirectTo url
-                window.location = redirectTo
-            else
-              # no redirect -> replace content with html from response
-              #exists = $.contains(document.documentElement, $element)
-              $element.replaceWith(data)
+
+      updateElement($element) if shouldUpdate
+

   # initialize the service
   @init= (options={}) ->
@@ -61,4 +63,10 @@
     interval = interval*1000 if interval < 1000

     # start update with interval
-    setInterval update, interval+    setInterval update, interval
+
+  @update= (name) ->
+    $("*[data-update-path*='#{name}']").each () -> updateElement(this)
+
+
+


fix typo

fix typo

--- app/assets/javascripts/stores/score_store.coffee
+++ app/assets/javascripts/stores/score_store.coffee
@@ -33,11 +33,11 @@

 initScore = ->
   for sheetId, _ of SheetStore.get()
-    scores[sheetId] ||=
-      state: Env.NO_PLAY
+    scores[parseInt sheetId] ||=
+      state: Env.NOPLAY
       display: ''
       sheetId: sheetId
-      color: Env.color[Env.NO_PLAY]
+      color: Env.color[Env.NOPLAY]

 ScoreStore.dispatchToken = AbilitysheetDispatcher.register (payload) ->
   action = payload.action


Fix topic creation form

Fix topic creation form

--- app/assets/javascripts/views/user_view.js.coffee
+++ app/assets/javascripts/views/user_view.js.coffee
@@ -112,7 +112,7 @@
       @selector.append @template(@post)

-  class window.TopicNewView
+  class window.TopicNewView extends CommonView
     constructor: ->
       @selector = $('.aside aside')
       do @clear_selector


Removed console output and alert before progress bar for rank the research topics

--- app/assets/javascripts/cleantheme/rankingUX.js.coffee
+++ app/assets/javascripts/cleantheme/rankingUX.js.coffee
@@ -20,7 +20,6 @@
       ,400
       , ->
         newWidth = parseFloat($(element2).find(".progress-bar").attr("aria-valuenow"))
-        console.log newWidth
         $(element1).removeClass "disabled"
         $(element1).addClass "voted"
         $(element2).addClass "in"
@@ -33,7 +32,6 @@
         ,100
         , ->
           newWidth = parseFloat($(element2).find(".progress-bar").attr("aria-valuenow"))
-          console.log newWidth
           $(element2).addClass "in"
           $(element2).find(".progress-bar").width newWidth + "%"
           $(element2).find(".progress-bar").html newWidth + "%"
@@ -42,7 +40,7 @@

 @userHasNoVotes = (element) ->
   alert = "
 You have used all of your votes! Come back tomorrow to vote more.
"
-  $(element).after alert
+  $(element).before alert


Indentation fixes

Indentation fixes

--- app/assets/javascripts/overlays/new_message.js.coffee
+++ app/assets/javascripts/overlays/new_message.js.coffee
@@ -19,7 +19,7 @@
       (div {id: 'new-message-overlay'},
         (header {},
           (h2 {},
-          (a {href: "#", className: 'message-color'}, @props.paperTitle))),
+            (a {href: "#", className: 'message-color'}, @props.paperTitle))),
         (main {},
           (RailsForm {action: "/papers/#{@props.paperId}/tasks.json", ref: 'form', method: 'POST'},
             (div {id: 'recipients'},
@@ -28,8 +28,7 @@
                   (img {src: "/images/profile-no-image.jpg"}))
                 (li {},
                   (Chosen {width: '150px'},
-                    @chosenOptions()
-                    )))),
+                    @chosenOptions() )))),
             (div {className: 'form-group'}, (input {type: 'text', placeholder: 'Type in a subject here'})),
             (div {className: 'form-group'},(textarea {placeholder: 'Type your message here'}))
           ))


collection sum now accepts a acumulator function

--- app/assets/javascripts/pinkman_base/collection.coffee
+++ app/assets/javascripts/pinkman_base/collection.coffee
@@ -28,15 +28,27 @@
   # Desc: sum a certain attribute of every element
   sum: (attr,callback) ->
     if attr?
-      v = 0
-      @each (obj) ->
-        if Pinkman.isNumber(obj[attr])
-          v = Number(obj[attr]) + v
-        else if obj[attr]?
-          v = obj[attr] + v
-      , =>
-        callback(this, v) if typeof callback == 'function'
-      return(v)
+      if $p.isString(attr)
+        v = 0
+        @each (obj) ->
+          if Pinkman.isNumber(obj[attr])
+            v = Number(obj[attr]) + v
+          else if obj[attr]?
+            v = obj[attr] + v
+        , =>
+          callback(this, v) if typeof callback == 'function'
+        return(v)
+      else if $p.isFunction(attr)
+        v = 0
+        @each (obj) ->
+          current = attr(obj)
+          if $p.isNumber(current)
+            v = Number(current) + v
+          else
+            v = current + v
+        , =>
+          callback(this, v) if typeof callback == 'function'
+        return(v)
     else
       callback(this,0) if typeof callback == 'function'
       return(0)


fix typo

fix typo

https://github.com/poooi/poi/issues/883#issuecomment-199131174

--- views/env-parts/data-resolver.coffee
+++ views/env-parts/data-resolver.coffee
@@ -241,10 +241,10 @@
             _ships[shipId].api_nowhp = _ships[shipId].api_maxhp
             _ships[shipId].api_cond = Math.max(40, _ships[shipId].api_cond)
         when '/kcsapi/api_req_practice/battle_result'
-          window._teitokuExp = body.api_experience
+          window._teitokuExp = body.api_member_exp
           window._teitokuLv = body.api_member_lv
         when '/kcsapi/api_req_sortie/battleresult'
-          window._teitokuExp = body.api_experience
+          window._teitokuExp = body.api_member_exp
           window._teitokuLv = body.api_member_lv
       event = new CustomEvent 'game.response',
         bubbles: true


Fix link_to_unless_current_span fix for call with block; __init_request_forgery_protection() to suck info from csrf meta tags

--- app/assets/javascripts/ultimate/helpers/url.js.coffee
+++ app/assets/javascripts/ultimate/helpers/url.js.coffee
@@ -3,7 +3,7 @@
 #= require ./javascript

 __char_encode = (char) -> "%#{char.charCodeAt(0).toString(16)}"
-escape_path = (str) -> str.replace(/[^*\-.0-9A-Z_a-z]/g, __char_encode).replace(/\+/g, '%20')
+__escape_path = (str) -> str.replace(/[^*\-.0-9A-Z_a-z]/g, __char_encode).replace(/\+/g, '%20')
 __string_encode = (str) -> _.map(str, (char) -> "&##{char.charCodeAt(0)};" ).join('')

 @Ultimate.Helpers.Url =
@@ -51,7 +51,10 @@
       else
         Ultimate.Helpers.Tag.content_tag('span', name ? url, html_options, false)
     else
-      @link_to arguments...
+      if block
+        @link_to options, html_options, block
+      else
+        @link_to name, options, html_options

   # TODO tests
   link_to_unless_current: (name, options = {}, html_options = {}, block = null) ->
@@ -77,7 +80,7 @@
     extras = _.compact _.map _.string.words('cc bcc body subject'), (item) ->
       option = _.outcasts.delete(html_options, item)
       if option?
-        "#{item}=#{escape_path(option)}"
+        "#{item}=#{__escape_path(option)}"
     extras = if _.isEmpty(extras) then '' else '?' + _.string.escapeHTML(extras.join('&'))
     email_address_obfuscated = email_address
     email_address_obfuscated = email_address_obfuscated.replace('@', _.outcasts.delete(html_options, 'replace_at'))   if 'replace_at' of html_options
@@ -128,8 +131,16 @@
     html_options

   __protect_against_forgery: false
+  __request_forgery_protection_token: 'form_token'
   __form_authenticity_token: 'secret'
-  __request_forgery_protection_token: 'form_token'
+
+  __init_request_forgery_protection: ->
+    param = $('head meta[name="csrf-param"]').attr('content')
+    token = $('head meta[name="csrf-token"]').attr('content')
+    if param and token
+      @__protect_against_forgery = true
+      @__request_forgery_protection_token = param
+      @__form_authenticity_token = token

   _token_tag: (token = @__form_authenticity_token) ->
     if token isnt false and @__protect_against_forgery


fixing args for @canUseTask

fixing args for @canUseTask

--- app/classifier/tasks/drawing-task-details-editor.cjsx
+++ app/classifier/tasks/drawing-task-details-editor.cjsx
@@ -48,7 +48,7 @@
           Question
         </button>
         {
-          if @canUseTask(@props.project, "text")
+          if @canUseTask("text")
             <button type="submit" className="minor-button" onClick={@handleAddTask.bind this, 'text'} title="Text tasks: the volunteer writes free-form text into a dialog box.">


Listen to channel messages through HipChat adapter

--- src/hubot/hipchat.coffee
+++ src/hubot/hipchat.coffee
@@ -58,7 +58,7 @@
     bot.onMessage (channel, from, message)->
       author = { name: from, reply_to: channel }
       hubot_msg = message.replace(mention, "#{self.name}: ")
-      self.receive new Robot.Message(author, hubot_msg)
+      self.receive new Robot.TextMessage(author, hubot_msg)
     bot.onPrivateMessage (from, message)=>
       user = self.userForId(from.match(/_(\d+)@/)[1])
       author = { name: user.name, reply_to: from }


rename user events for pusher

rename user events for pusher

--- models/current_user.coffee
+++ models/current_user.coffee
@@ -72,8 +72,7 @@

   subscribeToPusherEvents: ->
     user_pusher = mediator.shared.pusher.subscribe "user_#{@id}"
-    user_pusher.bind 'user:updated', (data) =>
-      console.log 'user:updated'
+    user_pusher.bind 'user-updated', (data) =>
       @set(data)
       mediator.trigger 'user:updated', this
     user_pusher.bind 'notification', (data) =>


Configures magellan to not use offset by height

our magellan is on the side, not on the top

--- app/assets/javascripts/init-foundation.js.coffee
+++ app/assets/javascripts/init-foundation.js.coffee
@@ -1,5 +1,5 @@
 $(document).on 'ready page:load', ->
   $ ->
-    $(document).foundation()
+    $(document).foundation("magellan-expedition": {"offset_by_height": false})
     return
-  return+  return


removed nifty behavior:itemCategoryEdit ajax request

--- app/assets/javascripts/shoppe/application.coffee
+++ app/assets/javascripts/shoppe/application.coffee
@@ -149,25 +149,6 @@
   beforeLoad: (el,options)->
     if selected_category = $.param($('.category_ids input').serializeArray())
       options['url'] = options['url']+'?'+selected_category
-  onLoad: (dialog,options)->
-    $(dialog).on 'submit', 'form', ->
-      form = $(this)
-      $.ajax
-        url: form.attr('action')
-        method: 'POST'
-        data: {
-          categories: $("#product_category_tree").jstree("get_checked", null, true),
-          "_method": "PUT"
-        }
-        dataType: 'text'
-        success: (data)->
-          window.location = window.location;
-        error: (xhr)->
-          if xhr.status == 422
-            alert xhr.responseText
-          else
-            alert 'Error'
-      false
   onClose: ->
     selected_category = $("#product_category_tree").jstree("get_checked", null, true).map (value) ->
       $('<input>').attr({'type': 'hidden', 'name': 'product[product_category_ids][]'}).val(value)


renamed find to select

renamed find to select

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -858,7 +858,7 @@
 		if @_context._parent
 			return @_context._parent

-	find: (selector) ->
+	select: (selector) ->
 		layers = _.find @descendants, (layer) ->
 			Utils.layerMatchesSelector(layer,selector)


Remove deprecated MongoClient connection options

--- lib/db.coffee
+++ lib/db.coffee
@@ -34,7 +34,7 @@

 connect = (callback) ->
   callbackCounter = 10
-  mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) ->
+  mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), (err, db) ->
     return callback(err) if err
     exports.mongoDb = db
     db.collection('tasks', (err, collection) ->


There's a addRemoveLinks option now.

There's a addRemoveLinks option now.

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -103,6 +103,12 @@
     # processQueue()
     enqueueForUpload: yes

+
+    # If true, Dropzone will add a link to each file preview to cancel/remove
+    # the upload.
+    # See dictCancelUpload and dictRemoveFile to use different words.
+    addRemoveLinks: no
+
     # A CSS selector or HTML element for the file previews container.
     # If null, the dropzone element itself will be used
     previewsContainer: null
@@ -128,6 +134,15 @@

     # If the server response was invalid.
     dictResponseError: "Server responded with {{statusCode}} code."
+
+    # If used, the text to be used for the cancel upload link.
+    dictCancelUpload: "Cancel upload"
+
+    # If used, the text to be used for confirmation when cancelling upload.
+    dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?"
+
+    # If used, the text to be used to remove a file.
+    dictRemoveFile: "Remove file"

     # If `done()` is called without argument the file is accepted
     # If you call it with an error message, the file is rejected
@@ -243,6 +258,17 @@
       file.previewElement.querySelector("[data-dz-name]").textContent = file.name
       file.previewElement.querySelector("[data-dz-size]").innerHTML = @filesize file.size

+      if @options.addRemoveLinks
+        file._removeLink = Dropzone.createElement """#{@options.dictRemoveFile}"""
+        file._removeLink.addEventListener "click", (e) =>
+          e.preventDefault()
+          e.stopPropagation()
+          if file.status == Dropzone.UPLOADING
+            @removeFile file if window.confirm @options.dictCancelUploadConfirmation
+          else
+            @removeFile file
+
+        file.previewElement.appendChild file._removeLink

     # Called whenever a file is removed.
     removedfile: (file) ->
@@ -270,6 +296,7 @@
     # Receives `file`
     processingfile: (file) ->
       file.previewElement.classList.add "dz-processing"
+      file._removeLink.textContent = @options.dictCancelUpload if file._removeLink

     # Called whenever the upload progress gets updated.
     # Receives `file`, `progress` (percentage 0-100) and `bytesSent`.
@@ -293,7 +320,9 @@

     # When the upload is finished, either with success or an error.
     # Receives `file`
-    complete: noop
+    complete: (file) ->
+      file._removeLink.textContent = @options.dictRemoveFile if file._removeLink
+


runtime: Pass id in runtime:runtime

runtime: Pass id in runtime:runtime

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -134,6 +134,8 @@

         if runtime.graph?.name
           r.graph = runtime.namespace + '/' + runtime.graph.name
+        if runtime.options.id?
+          r.id = runtime.options.id

         runtime.device.ping (err) ->
           if err


Set uuid for course and register using an invite

--- src/course/model.coffee
+++ src/course/model.coffee
@@ -8,9 +8,15 @@

 class Course

-  constructor: (@collectionUUID) ->
-    api.channel.on "course.#{@collectionUUID}.registration.complete", ({data}) =>
+  constructor: (attributes) ->
+    _.extend(@, attributes)
+
+    api.channel.on "course.#{@ecosystem_book_uuid}.registration.complete", ({data}) =>
       _.extend(this, data)

   register: (inviteCode) ->
-    api.channel.emit("course.#{@collectionUUID}.send.registration")
+    api.channel.emit("course.#{@ecosystem_book_uuid}.send.registration", data: {
+      book_cnx_id: @ecosystem_book_uuid, enrollment_code: inviteCode
+    })
+
+module.exports = Course


updateembedbox: add name to export

updateembedbox: add name to export

--- client/activity/lib/mixins/updateembedbox.coffee
+++ client/activity/lib/mixins/updateembedbox.coffee
@@ -5,7 +5,7 @@

 hasEmbed = (data) -> data.link? and Object.keys(data.link).length > 0

-module.exports =  ->
+module.exports = updateEmbedBoxMixin = ->

   data     = @getData()
   embedBox = if data.link?


GroupStackSettings: keep fetched credentials in memory

--- client/admin/lib/views/groupstacksettings.coffee
+++ client/admin/lib/views/groupstacksettings.coffee
@@ -32,6 +32,8 @@

     super options, data

+    @_credentials = {}
+

   createEditorPane: (content) ->

@@ -48,9 +50,9 @@
     @editorPane.setCss background: 'black'

-  createCredentialsBox: (credentials) ->
-
-    creds = ({title: c.title, value: c.publicKey} for c in credentials)
+  createCredentialsBox: ->
+
+    creds = ({title: c.title, value: c.publicKey} for c in @_credentials)

     @addSubView new kd.LabelView
       title: "Select credential to use:"
@@ -88,8 +90,10 @@

           return

+        @_credentials = credentials
+
         @createEditorPane stackTemplate?.template?.content or defaultTemplate
-        @createCredentialsBox credentials
+        @createCredentialsBox()

         @addSubView @saveButton = new kd.ButtonView
           title    : 'Set as default stack'


bugfix

bugfix

--- client/app/actions/message_action_creator.coffee
+++ client/app/actions/message_action_creator.coffee
@@ -141,7 +141,7 @@
                     type: ActionTypes.MESSAGE_MOVE_SUCCESS
                     value: {target, ref, updated}

-            callback error, updated
+            callback? error, updated

     mark: (target, flagAction, callback) ->
         ref = refCounter++


Correctly scopes selector

Correctly scopes selector

--- apps/examples/client/example_channel_view.coffee
+++ apps/examples/client/example_channel_view.coffee
@@ -3,10 +3,10 @@
 { default: FollowButton } = require '../../../react/components/FollowButton'

 module.exports = class ExampleChannelView extends Backbone.View
-
   initialize: ->
-    console.log('initializing', @$el.data('id'))
     @initializeFollowButton()

   initializeFollowButton: ->
-    mountWithApolloProvider FollowButton, { id: @$el.data('id'), type: 'CHANNEL' }, $('.js-example-channel-follow')+    props = { id: @$el.data('id'), type: 'CHANNEL' }
+    mountNode = @$('.js-example-channel-follow')
+    mountWithApolloProvider(FollowButton, props, mountNode)


work on kite ping method

work on kite ping method

--- client/landing/app/MainApp/account-mixins.coffee
+++ client/landing/app/MainApp/account-mixins.coffee
@@ -50,6 +50,7 @@

       cycleChannel =->
         @off()
+        @stopPinging()
         delete channels[@name]
         delete namesCache[@authenticationInfo.name]

@@ -94,12 +95,13 @@
           name        : "kite-#{kiteName}"
           clientId    : KD.remote.getSessionToken()
         channel.on "message", messageHandler.bind channel, kiteName
-        setInterval ->
+        i = setInterval ->
           channel.publish JSON.stringify
             method      : 'ping'
             arguments   : []
             callbacks   : {}
         , 5000
+        channel.stopPinging =-> clearInterval i

       # {utils} = KD


Chris' fix for 404 bug.

Chris' fix for 404 bug.

--- client/landing/app/MainApp/account-mixins.coffee
+++ client/landing/app/MainApp/account-mixins.coffee
@@ -90,7 +90,7 @@

       fetchChannel =(kiteName, callback)->
         channelName = getChannelName "kite-#{kiteName}"
-        return callback channels[channelName]  if channels[channelName]
+        return callback readyChannels[channelName]  if readyChannels[channelName]
         channel = KD.remote.mq.subscribe channelName
         kiteController = KD.getSingleton 'kiteController'
         kiteController.channels ?= {}


Perform default behaviour for external links when using middle click or ctrl, meta, or shift keys

--- assets/javascripts/views/external_link.js.coffee
+++ assets/javascripts/views/external_link.js.coffee
@@ -5,6 +5,9 @@
     super

     Marbles.DOM.on @el, 'click', (e) =>
+      middle_click = event.which == 2
+      return true if middle_click || e.ctrlKey || e.metaKey || e.shiftKey
+
       e.preventDefault()
       url = Marbles.DOM.attr(@el, 'href')
       window.open(url) if url


Changed tagline

Changed tagline

--- docpad.coffee
+++ docpad.coffee
@@ -3,7 +3,7 @@
   templateData:
     site:
       title: 'Tank Studios'
-      tagline: 'Life and Technology'
+      tagline: 'Walking with Jesus in a Modern World'
       description: 'Blogging about my walk with Jesus in a modern world'
       logo: '/img/logo.ico'
       url: 'http://blog.tankstudios.net/'