fix inability to click large discussed icon on project page

--- src/app/projects/projects.coffee
+++ src/app/projects/projects.coffee
@@ -42,7 +42,7 @@
       # roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin']
   )
 )
-.controller("ProjectsShowCtrl", ($scope, $stateParams, currentUser, UnitRole) ->
+.controller("ProjectsShowCtrl", ($scope, $stateParams, currentUser, UnitRole, Project, projectService, alertService) ->
   if $stateParams.authToken?
     # $scope.message = $stateParams.authToken
     currentUser.authenticationToken = $stateParams.authToken


Removed obsolete backend-client

Removed obsolete backend-client

--- src/backend-client.coffee
+++ src/backend-client.coffee
@@ -1,19 +0,0 @@
-Request = require 'superagent'
-CONFIG = require './config'
-
-module.exports = class BackendClient
-
-	@uploadFile : (fileModel, buffer, cb) ->
-		uploadURI = "#{CONFIG.backendURI}/upload/#{fileModel.md5}"
-		Request
-			.put(uploadURI)
-			.send(buffer)
-			.end (err, res) ->
-				if err
-					ret = new Error("Backend is down")
-					ret.cause = err
-					return cb ret
-				if res.status isnt 201
-					ret = new Error(res.text)
-					return cb ret
-				return cb()


Ensure valid background image

Ensure valid background image

--- resources/assets/coffee/_classes/current-user-observer.coffee
+++ resources/assets/coffee/_classes/current-user-observer.coffee
@@ -31,9 +31,9 @@
   setCovers: (elements) =>
     elements ?= @covers

-    url = if currentUser.id? then "url('#{currentUser.cover.url}')"
+    bgImage = if currentUser.id? then "url('#{currentUser.cover.url}')" else ''
     for el in elements
-      el.style.backgroundImage = url
+      el.style.backgroundImage = bgImage

   setData: (_e, data) =>


fix bad comments

fix bad comments

--- resources/assets/coffee/react/beatmap-discussions/post.coffee
+++ resources/assets/coffee/react/beatmap-discussions/post.coffee
@@ -138,13 +138,13 @@
       if targetUrl.host == currentUrl.host
         # target is osu site
         if targetUrl.pathname == currentUrl.pathname
-          # same beatmapset discussion, format: #123
+          # same beatmapset, format: #123
           linkText = targetUrl.hash.replace '/', ''
           text = text.replace(url, "#{linkText}")
           return
         else
           if beatmapset = targetUrl.pathname.match routeRegex
-            # different discussion, format: 1234#567
+            # different beatmapset, format: 1234#567
             linkText = "#{beatmapset[1]}#{targetUrl.hash}".replace '#/', '#'
             text = text.replace(url, "#{linkText}")
             return


Remove unused variable

Remove unused variable

--- resources/assets/coffee/react/beatmap-discussions/post.coffee
+++ resources/assets/coffee/react/beatmap-discussions/post.coffee
@@ -27,7 +27,6 @@
     @throttledUpdatePost = _.throttle @updatePost, 1000
     @handleKeyDown = InputHandler.textarea @handleKeyDownCallback
     @xhr = {}
-    @cache = {}
     @reviewEditor = React.createRef()

     @state =
@@ -38,10 +37,6 @@

   componentDidMount: =>
     osu.pageChange()
-
-
-  componentWillUpdate: =>
-    @cache = {}

   componentDidUpdate: =>


Add test for multiple simultaneous listComponents

--- spec/ComponentLoader.coffee
+++ spec/ComponentLoader.coffee
@@ -77,6 +77,23 @@
     unless noflo.isBrowser()
       # Browser component registry can be synchronous
       chai.expect(l.processing, 'should have started processing').to.equal true
+
+  describe 'calling listComponents twice simultaneously', ->
+    it 'should return the same results', (done) ->
+      loader = new noflo.ComponentLoader root
+      received = []
+      loader.listComponents (err, components) ->
+        return done err if err
+        received.push components
+        return unless received.length is 2
+        chai.expect(received[0]).to.equal received[1]
+        done()
+      loader.listComponents (err, components) ->
+        return done err if err
+        received.push components
+        return unless received.length is 2
+        chai.expect(received[0]).to.equal received[1]
+        done()

   describe 'after listing components', ->
     it 'should have the Graph component registered', ->


Stagehand global command; don't run if no project path

--- lib/dart-tools.coffee
+++ lib/dart-tools.coffee
@@ -66,7 +66,7 @@
     AnalysisDecorator = require './analysis/analysis_decorator'
     QuickInfoView = require './info/quick_info_view'
     ProblemView = require './info/problem_view'
-    Stagehand = require './stagehand/stagehand'
+

     @errorRepository = new ErrorRepository(@analysisApi)
     @analysisToolbar = new AnalysisToolbar(@errorRepository)
@@ -108,7 +108,14 @@
       Utils.dartSdkInfo (sdkInfo) =>
         @sdkInfo.showInfo(sdkInfo)

+  registerGlobalCommands: =>
+    Stagehand = require './stagehand/stagehand'
     atom.commands.add 'atom-workspace', 'dart-tools:stagehand', =>
+      if atom.project.getPaths().length is 0
+        atom.notifications.addInfo(
+          "[dart-tools] There is no open project to run Stagehand against."
+        )
+        return
       atom.notifications.addInfo 'Activating Stagehand...'
       Stagehand.activate().then => Stagehand.showProjectTemplates()

@@ -143,6 +150,7 @@
   activate: (state) ->
     @dartTools = new DartTools()
     @dartTools.waitForDartSources()
+    @dartTools.registerGlobalCommands()

     return unless Utils.isDartProject()
     @dartTools.boot()


:green_heart: Fix broken test due to CI DOM

--- spec/table-view-spec.coffee
+++ spec/table-view-spec.coffee
@@ -557,8 +557,8 @@
       editorOffset = editor.offset()

       expect(editor.length).toEqual(1)
-      expect(editorOffset.top).toBeCloseTo(cellOffset.top, -1)
-      expect(editorOffset.left).toBeCloseTo(cellOffset.left, -1)
+      expect(editorOffset.top).toBeCloseTo(cellOffset.top, -2)
+      expect(editorOffset.left).toBeCloseTo(cellOffset.left, -2)
       expect(editor.outerWidth()).toEqual(cell.outerWidth())
       expect(editor.outerHeight()).toEqual(cell.outerHeight())


Don't retain cursor position when it is at the end of the file

--- spec/whitespace-spec.coffee
+++ spec/whitespace-spec.coffee
@@ -93,10 +93,10 @@
       expect(editor.getText()).toBe "no trailing newline"

     it "does not move the cursor when the new line is added", ->
-      editor.insertText "foo"
-      expect(editor.getCursorBufferPosition()).toEqual([0,3])
+      editor.insertText "foo\nboo"
+      editor.setCursorBufferPosition([0,3])
       editor.save()
-      expect(editor.getText()).toBe "foo\n"
+      expect(editor.getText()).toBe "foo\nboo\n"
       expect(editor.getCursorBufferPosition()).toEqual([0,3])

   describe "GFM whitespace trimming", ->


Updated clear database unit test with the new Somewhere API

--- specs/jsonServerSpec.coffee
+++ specs/jsonServerSpec.coffee
@@ -2,7 +2,7 @@

 expect     = require('chai').expect
 request    = require 'supertest'
-db         = require 'somewhere'
+DB         = require 'somewhere'
 jsonserver = require '../lib/jsonserver'

 app = jsonserver.init
@@ -62,4 +62,5 @@
         do done

   it "should delete database", ->
+    db = new DB 'test.json'
     do db.clear


enhance

enhance

--- scripts/memes.coffee
+++ scripts/memes.coffee
@@ -21,6 +21,9 @@

   robot.hear /da+yum/i, (msg) ->
     msg.send 'http://fc09.deviantart.net/fs71/f/2012/258/8/2/dayum_dayum_dayum___by_mystic_dranomimon-d5erzlw.gif'
+
+  robot.hear /\benhance\b/i, (msg) ->
+    msg.send 'http://stream1.gifsoup.com/view1/2373799/enhance-o.gif'

   robot.hear /follow your dreams/i, (msg) ->
     msg.send 'http://i.chzbgr.com/maxW500/7085726208/h2DE3C7F0.gif'


Add new memes

Add new memes

--- src/memes.coffee
+++ src/memes.coffee
@@ -26,7 +26,9 @@
   "ants": "Do You Want Ants? (archer)",
   "awesome": "Socially Awesome Penguin",
   "awkward": "Socially Awkward Penguin",
+  "bad": "You Should Feel Bad",
   "badchoice": "Milk Was a Bad Choice (anchorman)",
+  "bd": "Butthurt Dweller",
   "biw": "BABY Insanity Wolf",
   "blb": "Bad Luck Brian",
   "boat": "I should buy a boat cat",
@@ -39,19 +41,24 @@
   "ch": "Captain Hindsight",
   "chosen": "You Were the Chosen One!",
   "crazypills": "I Feel Like I'm Taking Crazy Pills",
+  "dodgson": "See? Nobody Cares",
   "doge": "Doge",
+  "dsm": "Dating Site Murderer",
   "dwight": "Schrute Facts",
   "elf": "You Sit on a Throne of Lies",
   "ermg": "Ermahgerd",
   "fa": "Forever Alone",
   "fbf": "Foul Bachelor Frog",
   "fetch": "Stop Trying to Make Fetch Happen",
+  "fmr": "F*** Me, Right?",
   "fry": "Futurama Fry",
   "fwp": "First World Problems",
   "ggg": "Goog Guy Greg",
   "hipster": "Hipster Barista",
   "icanhas": "I can Has Cheezburger",
+  "inigo": "Inigo Montoya",
   "interesting": "The Most Interesting Man in the World",
+  "its-happening": "It's Happening",
   "ive": "Jony Ive Redesigns Things",
   "iw": "Insanity Wolf",
   "jetpack": "Jetpack Guy / Nothing to Do Here",
@@ -62,15 +69,22 @@
   "live": "Do It Live!",
   "ll": "Laughing Lizard",
   "mmm": "Minor Mistake Marvin",
+  "money": "Shut Up and Take My Money!",
   "mordor": "One Does Not Simply Walk into Mordor",
   "morpheus": "Matrix Morpheus",
+  "oag": "Overly Attached Girlfriend",
   "officespace": "That Would be Great",
-  "oag": "Overly Attached Girlfriend",
+  "older": "An Older Code Sir, But It Checks Out",
   "oprah": "Oprah You Get a Car",
   "philosoraptor": "Philosoraptor",
   "red": "Oh, Is that what we're going to do today?",
   "regret": "I Immediately Regret This Decision (anchorman)",
   "remembers": "Pepperidge Farm Remembers",
+  "sad-biden": "Sad Joe Biden",
+  "sad-boehner": "Sad John Boehner",
+  "sad-bush": "Sad George Bush",
+  "sad-clinton": "Sad Bill Clinton",
+  "sad-obama": "Sad Barack Obama",
   "sadfrog": "Sad Frog / Feels Bad Man",
   "sarcasticbear": "Sarcastic Bear",
   "sb": "Scumbag Brain",
@@ -82,10 +96,12 @@
   "success": "Success Kid",
   "tenguy": "[10] Guy",
   "toohigh": "The Rent is Too Damn High",
+  "tried": "At Least You Tried",
   "winter": "Prepare yourselves, winter is coming",
   "wonka": "Condescending Wonka",
   "xy": "X all the Y",
   "yallgot": "Y'All Got Anymore of",
+  "yodawg": "Xzibit Yo Dawg",
   "yuno": "Y U NO?"

 checkCode = (meme, memes) ->


draw shapes in the mini map

draw shapes in the mini map

--- public/javascripts/tetris.coffee
+++ public/javascripts/tetris.coffee
@@ -41,7 +41,7 @@
       @map = new Map(0, Tetris.viewport.y*Tetris.gridSize+1, @blocks.blocks, @shapes)
       gs.addEntity(@map)
       pusher.connection.bind 'connected', ->
-        gs.addEntity(Tetris.Shape.randomShape(x:Tetris.initialShapeOffset(), y:0, color: {r:0, g:0, b:0}, owned: true))
+        gs.addEntity(Tetris.Shape.randomShape(x: Tetris.initialShapeOffset(), y:0, color: {r:0, g:0, b:0}, owned: true))

   class Map
     constructor: (@x, @y, @blocks, @shapes) ->
@@ -51,20 +51,23 @@
       @drawShapes(c)
       @drawBounds(c)
     drawShapes: (c) ->
-
+      for id, shape of @shapes
+        for i in [0,1,2,3]
+          block = shape.blockPosition(i)
+          @drawBlock(c, block.x, block.y, '#b00')
     drawBlocks: (c) ->
-      @drawBlock(c, block.x, block.y) for block in @blocks
-    drawBlock: (c, x, y) ->
-      c.fillStyle = "#00b"
+      @drawBlock(c, block.x, block.y, "#00b") for block in @blocks
+    drawBlock: (c, x, y, color) ->
+      c.fillStyle = color
       x = @x + (@gridSize * x)
       y = @y + (@gridSize * y)
       c.fillRect(x, y, @gridSize, @gridSize)
     drawBounds: (c) ->
       c.strokeStyle = "#0f0"
       start = @x + (Tetris.viewportOffset.x * @gridSize)
-      end = start + (Tetris.viewport.x * @gridSize)
+      width = Tetris.viewport.x * @gridSize
       height = Tetris.viewport.y * @gridSize
-      c.strokeRect(start, @y, end, height)
+      c.strokeRect(start, @y, width, height)

   class Level
     constructor: ->


support creating limit orders in the api endpoint

--- lib/ews/ew_api.coffee
+++ lib/ews/ew_api.coffee
@@ -61,6 +61,12 @@
       amount: amount
       currency: currency

+  place_limit_order: (account_id, order) =>
+    order.account = account_id
+    order.kind = "CREATE_LIMIT_ORDER"
+    @info "CREATING LIMIT ORDER #{JSON.stringify(order)}"
+    @engine.execute_operation(order)
+
   get_balances: (account_id) =>
     @info "SENDING GET_BALANCES"
     @query.execute_operation


prefer conversation name over participant list. close #2

--- src/model.coffee
+++ src/model.coffee
@@ -62,7 +62,7 @@
           @identityAdd participant.id.chat_id, participant.fallback_name
           participants.push participant.id.chat_id
           names.push participant.fallback_name
-      name = names.join(", ")
+      name = conversation.name ? names.join(", ")
       @conversationAdd id, name, participants, sort_timestamp
     # extract messages
     data.conversation_state.forEach (conversation) =>
@@ -73,7 +73,7 @@
         else
           #console.log 'unhandled message type'
           #console.log JSON.stringify event, null, '  '
-
+

 status = new Status()
 status.test = -> console.log 'asf'


Delete poo-tracker_test.coffee

Delete poo-tracker_test.coffee

--- tests/poo-tracker_test.coffee
+++ tests/poo-tracker_test.coffee
@@ -1,15 +0,0 @@
-assert = require 'assert'
-
-Helper = require('hubot-test-helper')
-process.env.POO_REDIS_URL = "redis://h:pass@localhost:6379/0"
-helper = new Helper('../scripts/poo-tracker.coffee')
-
-describe 'poo-tracker', ->
-  room = null
-
-  beforeEach ->
-    room = helper.createRoom()
-    room.user.say 'shithead', '@hubot hello, this is dog'
-
-  it 'compiled if it got this fucking far', ->
-    assert room.messages.length > 0


Buffers OR Filenames

Buffers OR Filenames

--- lib/image.coffee
+++ lib/image.coffee
@@ -9,10 +9,13 @@
 PNG = require './image/png'

 class PDFImage
-    @open: (filename) ->
-        @contents = fs.readFileSync filename
-        return unless @contents
-
+    @open: (filenameOrBuffer) ->
+        if typeof filenameOrBuffer is 'object' and filenameOrBuffer instanceof Buffer
+            @contents = filenameOrBuffer
+        else
+            @contents = fs.readFileSync filenameOrBuffer
+            return unless @contents
+
         @data = new Data @contents
         @filter = null


handle remove files without associated element

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab-multiple.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab-multiple.js.coffee
@@ -155,7 +155,8 @@
       @__fileRemoved(oldFile)

     __fileToEl: (file) ->
-      $(file).data('dpm-el')
+      # File can be removed before.
+      $(file).data('dpm-el') or $()

     __createFileEl: (file) ->
       fileEl = @__fileTpl.clone()


fix logic in mongo casting to doc

fix logic in mongo casting to doc

--- lib/mongo.coffee
+++ lib/mongo.coffee
@@ -51,10 +51,10 @@
   return query

 castToDoc = (docName, data) ->
-  doc = if data.data && typeof data.data is 'object' && !Array.isArray(data.data)
+  doc = if typeof data.data is 'object' && data.data isnt null && !Array.isArray(data.data)
     data.data
   else
-    _data: data.data || null
+    _data: if data.data? then data.data else null
   doc._type = data.type || null
   doc._v = data.v
   doc._id = docName


Add a method to discern movie type.

Add a method to discern movie type.

--- lib/movie.coffee
+++ lib/movie.coffee
@@ -8,4 +8,18 @@

 class Movie
-  constructor : (string_of_id_or_unixtime) ->+  constructor : (string_of_id_or_unixtime) ->
+    @.set_type string_of_id_or_unixtime
+
+  set_type : (string_of_id_or_unixtime) ->
+    if _(string_of_id_or_unixtime).startsWith 'nm'
+      @type = 'Niconico Movie Maker'
+    else if _(string_of_id_or_unixtime).startsWith 'sm'
+      @type = 'Smile Video'
+    else
+      @type = 'unknown'
+
+
+module.exports = Movie
+m = new Movie 'sm17683648'
+console.log m


Reorganise config to add css editor specific settings

--- lib/tablr.coffee
+++ lib/tablr.coffee
@@ -4,61 +4,68 @@

 module.exports =
   config:
-    undefinedDisplay:
-      type: 'string'
-      default: ''
-    pageMoveRowAmount:
-      type: 'integer'
-      default: 20
-    pageMoveColumnAmount:
-      type: 'integer'
-      default: 5
-    scrollSpeedDuringDrag:
-      type: 'integer'
-      default: 20
-    scrollPastEnd:
-      type: 'boolean'
-      default: false
-
-    rowHeight:
-      type: 'integer'
-      default: 24
-    rowOverdraw:
-      type: 'integer'
-      default: 3
-    minimumRowHeight:
-      type: 'integer'
-      default: 16
-    rowHeightIncrement:
-      type: 'integer'
-      default: 20
-
-    columnWidth:
-      type: 'integer'
-      default: 120
-    columnOverdraw:
-      type: 'integer'
-      default: 2
-    minimumColumnWidth:
-      type: 'integer'
-      default: 40
-    columnWidthIncrement:
-      type: 'integer'
-      default: 20
-
-    flattenBufferMultiSelectionOnPaste:
-      type: 'boolean'
-      default: false
-      description: 'If the clipboard content comes from a multiple selection copy in a text editor, the whole clipboard text will be pasted in each cell of the table selection.'
-    distributeBufferMultiSelectionOnPaste:
-      type: 'string'
-      default: 'vertically'
-      enum: ['horizontally', 'vertically']
-      description: 'If the clipboard content comes from a multiple selection copy in a text editor, each selection will be considered as part of the same column (`vertically`) or of the same row (`horizontally`).'
-    treatEachCellAsASelectionWhenPastingToABuffer:
-      type: 'boolean'
-      default: true
-      description: 'When copying from a table to paste the content in a text editor this setting will make each cell appear as if they were created from different selections.'
+    tableEditor:
+      type: 'object'
+      properties:
+        undefinedDisplay:
+          type: 'string'
+          default: ''
+        pageMoveRowAmount:
+          type: 'integer'
+          default: 20
+        pageMoveColumnAmount:
+          type: 'integer'
+          default: 5
+        scrollSpeedDuringDrag:
+          type: 'integer'
+          default: 20
+        scrollPastEnd:
+          type: 'boolean'
+          default: false
+
+        rowHeight:
+          type: 'integer'
+          default: 24
+        rowOverdraw:
+          type: 'integer'
+          default: 3
+        minimumRowHeight:
+          type: 'integer'
+          default: 16
+        rowHeightIncrement:
+          type: 'integer'
+          default: 20
+
+        columnWidth:
+          type: 'integer'
+          default: 120
+        columnOverdraw:
+          type: 'integer'
+          default: 2
+        minimumColumnWidth:
+          type: 'integer'
+          default: 40
+        columnWidthIncrement:
+          type: 'integer'
+          default: 20
+
+    copyPaste:
+      type: 'object'
+      properties:
+        flattenBufferMultiSelectionOnPaste:
+          type: 'boolean'
+          default: false
+          description: 'If the clipboard content comes from a multiple selection copy in a text editor, the whole clipboard text will be pasted in each cell of the table selection.'
+        distributeBufferMultiSelectionOnPaste:
+          type: 'string'
+          default: 'vertically'
+          enum: ['horizontally', 'vertically']
+          description: 'If the clipboard content comes from a multiple selection copy in a text editor, each selection will be considered as part of the same column (`vertically`) or of the same row (`horizontally`).'
+        treatEachCellAsASelectionWhenPastingToABuffer:
+          type: 'boolean'
+          default: true
+          description: 'When copying from a table to paste the content in a text editor this setting will make each cell appear as if they were created from different selections.'
+
     supportedCsvExtensions:
       type: 'array'
       default: ['csv', 'tsv']
@@ -68,6 +75,26 @@
       default: 'alphabetic'
       enum: ['alphabetic', 'numeric', 'numericZeroBased']
       description: 'When file has no header, select the default naming method for the columns. `alphabetic` means use A, B,…, Z, AA, AB… `numeric` is for simple numbers, ie 1, 2… `numericZeroBased` is similar to `numeric`, except that it starts numbering from 0 instead of 1'
+
+    csvEditor:
+      type: 'object'
+      properties:
+        columnDelimiter:
+          type: 'string'
+          default: ','
+        rowDelimiter:
+          type: 'string'
+          default: 'auto'
+        quotes:
+          type: 'string'
+          default: '"'
+        escape:
+          type: 'string'
+          default: '"'
+        comments:
+          type: 'string'
+          default: '#'
+

   activate: ({csvConfig}) ->
     @csvConfig = new CSVConfig(csvConfig)


Support inline code comments

Support inline code comments

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -79,12 +79,19 @@
     singleLineMatcher = ///^\s*(#{language.singleLineComment.join('|')})\s?(.*)$///

     for line in lines
-      if match = line.match singleLineMatcher
+      # Match that line to the language's single line comment syntax.
+      #
+      # However, we treat all comments beginning with } as inline code commentary.
+      match = line.match singleLineMatcher
+
+      #} For example, this comment should be treated as part of our code.
+      if match? and match[2]?[0] != '}'
         if currSegment.code.length > 0
           segments.push currSegment
           currSegment = new @Segment

         currSegment.comments.push match[2]
+
       else
         currSegment.code.push line

@@ -122,7 +129,7 @@
       result += data.toString()

     pygmentize.addListener 'exit', (args...) =>
-      # pygments spits it out wrapped in

...

.  We want to
+      # pygments spits it out wrapped in `<div class="highlight"><pre>...</pre></div>`.  We want to
       # manage the styling ourselves, so remove that.
       result = result.replace('

', '').replace('

', '')


making tab-complete async, and in general better

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -217,7 +217,7 @@

 # helper function for 'ls'
 read_dir = (dir, pretty=true, columns=true, cb) ->
-  node.fs.readdir dir, (err, contents) ->
+  node.fs.readdir node.path.resolve(dir), (err, contents) ->
     if err or contents.length is 0 then return cb('')
     contents = contents.sort()
     return cb(contents.join('\n')) unless pretty
@@ -380,14 +380,14 @@
     return null
   cd: (args) ->
     if args.length > 1 then return "Usage: cd <directory>"
-    if args.length == 0 then args.push("~")
+    dir = if args.length == 0 then '/home/doppio' else node.path.resolve(args[0])
     # Verify path exits before going there. chdir does not verify that the
     # directory exists.
-    node.fs.exists args[0], (doesExist) ->
+    node.fs.exists dir, (doesExist) ->
       if doesExist
-        node.process.chdir(args[0])
+        node.process.chdir(dir)
       else
-        controller.message "Directory #{args[0]} does not exist.\n", 'error', true
+        controller.message "Directory #{dir} does not exist.\n", 'error', true
       controller.reprompt()
     return null
   rm: (args) ->
@@ -468,27 +468,34 @@
 tabComplete = ->
   promptText = controller.promptText()
   args = promptText.split /\s+/
-  prefix = longestCommmonPrefix(getCompletions(args))
-  return if prefix == ''  # TODO: if we're tab-completing a blank, show all options
-  # delete existing text so we can do case correction
-  promptText = promptText.substr(0, promptText.length - util.last(args).length)
-  controller.promptText(promptText + prefix)
-
-getCompletions = (args) ->
-  if args.length is 1 then commandCompletions args[0]
-  else if args[0] is 'time' then getCompletions(args[1..])
-  else fileNameCompletions args[0], args
-
-commandCompletions = (cmd) ->
-  (name for name of commands when name.substr(0, cmd.length) is cmd)
-
-fileNameCompletions = (cmd, args) ->
-  validExtension = (fname) ->
-    dot = fname.lastIndexOf('.')
-    ext = if dot is -1 then '' else fname.slice(dot+1)
-    if cmd is 'javac' then ext is 'java'
-    else if cmd is 'javap' or cmd is 'java' then ext is 'class'
-    else true
+  getCompletions args, (completions) ->
+    prefix = longestCommmonPrefix(completions)
+    return if prefix == ''  # TODO: if we're tab-completing a blank, show all options
+    # delete existing text so we can do case correction
+    promptText = promptText.substr(0, promptText.length - util.last(args).length)
+    controller.promptText(promptText + prefix)
+
+getCompletions = (args, cb) ->
+  if args.length is 1
+    cb filterSubstring(args[0], Object.keys(commands))
+  else if args[0] is 'time'
+    getCompletions(args[1..], cb)
+  else
+    fileNameCompletions args[0], args, cb
+  return
+
+filterSubstring = (prefix, lst) ->
+  (x for x in lst when x.substr(0, prefix.length) is prefix)
+
+validExtension = (cmd, fname) ->
+  dot = fname.lastIndexOf('.')
+  ext = if dot is -1 then '' else fname.slice(dot+1)
+  if cmd is 'javac' then ext is 'java'
+  else if cmd is 'javap' or cmd is 'java' then ext is 'class'
+  else if cmd is 'cd' then false
+  else true
+
+fileNameCompletions = (cmd, args, cb) ->
   chopExt = args.length == 2 and (cmd is 'javap' or cmd is 'java')
   toComplete = util.last(args)
   lastSlash = toComplete.lastIndexOf('/')
@@ -498,24 +505,24 @@
   else
     dirPfx = ''
     searchPfx = toComplete
-  try
-    dirList = node.fs.readdirSync(if dirPfx == '' then '.' else dirPfx)
-    # Slight cheat.
-    dirList.push('..')
-    dirList.push('.')
-  catch e
-    return []
-
-  completions = []
-  for item in dirList
-    isDir = node.fs.statSync(dirPfx + item)?.isDirectory()
-    continue unless validExtension(item) or isDir
-    if item.slice(0, searchPfx.length) == searchPfx
-      if isDir
-        completions.push(dirPfx + item + '/')
-      else if cmd != 'cd'
-        completions.push(dirPfx + (if chopExt then item.split('.',1)[0] else item))
-  completions
+  dirPath = if dirPfx == '' then '.' else node.path.resolve(dirPfx)
+  node.fs.readdir dirPath, (err, dirList) ->
+    return cb([]) if err?
+    dirList = filterSubstring searchPfx, dirList
+    completions = []
+    num_back = 0
+    for item in dirList
+      do (item) ->
+        node.fs.stat node.path.resolve(dirPfx+item), (err, stats) ->
+          if err?
+          else if stats.isDirectory()
+            completions.push(dirPfx + item + '/')
+          else if validExtension(cmd, item)
+            completions.push(dirPfx + (if chopExt then item.split('.',1)[0] else item))
+          if ++num_back == dirList.length
+            cb(completions)
+    return  # void function
+  return  # void function

 # use the awesome greedy regex hack, from http://stackoverflow.com/a/1922153/10601
 longestCommmonPrefix = (lst) -> lst.join(' ').match(/^(\S*)\S*(?: \1\S*)*$/i)[1]


fixes test description

fixes test description

--- tests/unit/object.unit.coffee
+++ tests/unit/object.unit.coffee
@@ -99,7 +99,7 @@
       it "can return the super class", ->
         expect(Sub.superClass()).to.equal(Base)

-      it "returns null if there is no super class", ->
+      it "returns undefined if there is no super class", ->
         expect(Space.Object.superClass()).to.equal(undefined)

       it "can return a static prop or method of the super class", ->


Checking if `wd.remote sauce` is being called

--- tests/www/tests/runner.coffee
+++ tests/www/tests/runner.coffee
@@ -35,6 +35,7 @@
   username: process.env.SAUCE_USERNAME
   accessKey: process.env.SAUCE_ACCESS_KEY

+
 # starts server
 server.start coverage

@@ -65,6 +66,7 @@
       if env is 'local'
         browser = do wd.remote
       else
+        console.log 'using sauce labs'
         browser = wd.remote sauce_conf

       # SET MOCHA HOOKS


improved dispatch method

improved dispatch method

--- src/qqbot.coffee
+++ src/qqbot.coffee
@@ -15,7 +15,7 @@
         @buddy_info = {}
         @group_info = {}
         @groupmember_info = {}
-
+        @plugin_control = plugin

     # @format PROTOCOL `用户分组信息格式`
@@ -66,12 +66,14 @@
     # @param content:string 回复信息
     # @callback ret, error
     reply_message: (message, content, callback)->
+        log "发送消息：",content
         if message.type == 'group'
             api.send_msg_2group  message.from_gid , content , @auth, (ret,e)->
                 callback(ret,e) if callback
         else if message.type == 'buddy'
             api.send_msg_2buddy message.from_uin , content , @auth , (ret,e)->
-                callback(ret,e) if callback
+                callback(ret,e) if callback
+

     # 处理poll返回的内容
     handle_poll_responce: (resp)->
@@ -95,7 +97,14 @@

         # 消息处理 ，只操作群
         # if msg.type == 'group'
-        plugin.dispatch(@, msg)
+
+        content = msg.content.trim()
+        replied = false
+        reply = (content)=>
+            @reply_message(msg,content) unless replied
+            replied = true
+
+        @plugin_control.dispatch(content ,reply, @ , msg)


SVG Element handling in isOrphan

SVG Element handling in isOrphan

jQuery does not properly report on :hidden for SVG elements. They have not intent of fixing that since they officially do not support svg. Bootstrap-tour works nonetheless with this small fix.

--- src/coffee/bootstrap-tour.coffee
+++ src/coffee/bootstrap-tour.coffee
@@ -291,7 +291,8 @@
         document.location.href = path

     _isOrphan: (step) ->
-      ! step.element? || ! $(step.element).length || $(step.element).is(":hidden")
+      # Do not check for is(":hidden") on svg elements. jQuery does not work properly on svg.
+      ! step.element? || ! $(step.element).length || $(step.element).is(":hidden") && ($(step.element)[0].namespaceURI != "http://www.w3.org/2000/svg")

     # Show step popover
     _showPopover: (step, i) ->
@@ -500,4 +501,4 @@

   window.Tour = Tour

-)(jQuery, window)+)(jQuery, window)


Clean up Highlights plugin and fix test suites

Add a clean, isolated set of tests for the new Highlights plugin. The
existing tests were horrific, and extensively tested implementation
rather than interface. The new tests are much clearer.

This commit also makes the drawAll, draw, undraw, redraw methods on the
Highlights plugin public.

--- src/range.coffee
+++ src/range.coffee
@@ -1,6 +1,7 @@
 xpath = require('./xpath')
 Util = require('./util')
 $ = Util.$
+_t = Util.TranslationString

 Range = {}


Add pprint utility function for JSON-based structures

--- src/coffee/cilantro/utils.coffee
+++ src/coffee/cilantro/utils.coffee
@@ -33,4 +33,7 @@
         obj[last] = value
         return

-    $.extend { getDotProp, setDotProp }, mods...
+    pprint = (obj) ->
+        console.log(JSON.stringify(obj, null, 4))
+
+    $.extend { getDotProp, setDotProp, pprint }, mods...


fromPointWithDelta() to private

fromPointWithDelta() to private

--- src/range.coffee
+++ src/range.coffee
@@ -64,7 +64,7 @@
       endPoint.column += lines[0].length
     new this(startPoint, endPoint)

-  # Public: Returns a {Range} that starts at the given point and ends at the
+  # Returns a {Range} that starts at the given point and ends at the
   # start point plus the given row and column deltas.
   #
   # * `startPoint` A {Point} or point-compatible {Array}


[options] no longer switch text when hovering over buttons

--- src/coffee/options.coffee
+++ src/coffee/options.coffee
@@ -428,27 +428,6 @@
                         .button 'reset'
                         .removeClass 'disable-hover'

-        [ $('#enable_anomalies'), $('button[data-match]') ].each ($buttons) ->
-            $buttons
-                .on 'mouseenter', () ->
-                    $this = $ @
-
-                    return if $this.hasClass 'disable-hover'
-
-                    if $this.hasClass 'active'
-                        $this.text module._('disabled', 'Disabled')
-                    else
-                        $this.text module._('enabled', 'Enabled')
-                .on 'mouseleave', () ->
-                    $this = $ @
-
-                    return if $this.hasClass 'disable-hover'
-
-                    if $this.hasClass 'active'
-                        $this.text module._('enabled', 'Enabled')
-                    else
-                        $this.text module._('disabled', 'Disabled')
-
         updateButtons()

         module.comm.setOnUpdate ->


Fix compiler variable conflicts that are possible with coffeescript >= 1.9.1

--- src/coffeeCoverage.coffee
+++ src/coffeeCoverage.coffee
@@ -408,10 +408,22 @@

         quotedFileName = toQuotedString fileName

+        coffeeOptions = {
+            bare: effectiveOptions.bare
+            literate: literate
+        }
+
         try
-            ast = coffeeScript.nodes(fileData, {literate: literate})
+            tokens = coffeeScript.tokens fileData, coffeeOptions
+
+            # collect referenced variables
+            coffeeOptions.referencedVars = (token[1] for token in tokens when token.variable)
+
+            # convert tokens to ast
+            ast = coffeeScript.nodes(tokens)
         catch err
             throw new CoverageError("Could not parse #{fileName}: #{err.stack}")
+

         # Add coverage instrumentation nodes throughout the tree.
         instrumentedLines = []
@@ -515,7 +527,7 @@

         # Compile the instrumented CoffeeScript and write it to the JS file.
         try
-            js = ast.compile {bare: effectiveOptions.bare, literate: literate}
+            js = ast.compile coffeeOptions
         catch err
             throw new CoverageError("Could not compile #{fileName} after annotating: #{err.stack}")


Bugfix: dependency documentation

Bugfix: dependency documentation

--- src/scripts/9gag.coffee
+++ src/scripts/9gag.coffee
@@ -3,7 +3,7 @@
 #
 # Dependencies:
 #   "htmlparser": "1.7.6"
-#   "soupselect: "0.2.0"
+#   "soupselect": "0.2.0"
 #
 # Configuration:
 #   None


Fix syntax error when using typeof

Fix syntax error when using typeof

--- src/scripts/auth.coffee
+++ src/scripts/auth.coffee
@@ -42,7 +42,7 @@
     hasRole: (user, roles) ->
       user = robot.brain.userForId(user.id)
       if user? and user.roles?
-        roles = [roles] if roles typeof String
+        roles = [roles] if typeof roles is 'string'
         for role in roles
           return true if role in user.roles
       return false


Tune screenshake even more

Tune screenshake even more

--- src/components/ScrollWall.coffee
+++ src/components/ScrollWall.coffee
@@ -63,13 +63,13 @@
       x = 0
       y = 0
       if screenshake > 0
-        MAX_X_OFFSET = 80
-        MAX_Y_OFFSET = 60
+        MAX_X_OFFSET = 40
+        MAX_Y_OFFSET = 30

         @time += fd.dt

-        xn = noise.perlin2(0.1, @time / ((1.3 - @trauma) * 20))
-        yn = noise.perlin2(@time / ((1.3 - @trauma) * 20), 0.3)
+        xn = noise.perlin2(0.1, @time / ((3 - @trauma) * 160))
+        yn = noise.perlin2(@time / ((3 - @trauma) * 160), 0.3)
         x = 0 + Math.round(MAX_X_OFFSET * screenshake * xn)
         y = 0 + Math.round(MAX_Y_OFFSET * screenshake * yn)


Escape angle brackets in help http route output

--- src/scripts/help.coffee
+++ src/scripts/help.coffee
@@ -52,7 +52,8 @@

 module.exports = (robot) ->
   robot.respond /help\s*(.*)?$/i, (msg) ->
-    cmds = robot.helpCommands()
+    cmds = robot.helpCommands().map (cmd) ->
+      cmd.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')

     if msg.match[1]
       cmds = cmds.filter (cmd) ->


Update and pause handling

Update and pause handling

--- src/scripts/play.coffee
+++ src/scripts/play.coffee
@@ -6,6 +6,7 @@
 	constructor: (game) ->
 		@player = null
 		@spawnTimer = null
+		@mobGroup = null

 		@fontStyle = null
 		@scoreText = null
@@ -21,30 +22,55 @@
 		@mobs = new MobManager()

 		# physics and gravity
-		@physics.startSystem(Phaser.Physics.ARCADE);
-		@physics.arcade.gravity.y = 200;
+		@physics.startSystem Phaser.Physics.ARCADE
+		@physics.arcade.gravity.y = 200

 		# display images
-		@add.sprite(0, 0, 'background');
-		@add.sprite(-30, Axe.GAME_HEIGHT-160, 'floor');
-		@add.sprite(10, 5, 'score-bg');
+		@add.sprite 0, 0, 'background'
+		@add.sprite -30, Axe.GAME_HEIGHT-160, 'floor'
+		@add.sprite 10, 5, 'score-bg'

 		# add pause button
-		@add.button(Axe.GAME_WIDTH-96-10, 5, 'button-pause', @managePause, this);
+		@add.button Axe.GAME_WIDTH-96-10, 5, 'button-pause', @managePause, this

 		# create the player and animations
-		@player = @add.sprite(5, 760, 'monster-idle');
-		@player.animations.add('idle', [0,1,2,3,4,5,6,7,8,9,10,11,12], 10, true);
-		@player.animations.play('idle');
+		@player = @add.sprite 5, 760, 'monster-idle'
+		@player.animations.add 'idle', [0,1,2,3,4,5,6,7,8,9,10,11,12], 10, true
+		@player.animations.play 'idle'

 		# set fonts
-		@fontStyle = { font: "40px Arial", fill: "#FFCC00", stroke: "#333", strokeThickness: 5, align: "center" };
+		@fontStyle = { font: "40px Arial", fill: "#FFCC00", stroke: "#333", strokeThickness: 5, align: "center" }

 		# initialise variables
-		@spawnTimer = 0;
-		@scoreText = @add.text(120, 20, "0", @fontStyle);
+		@spawnTimer = 0
+		@mobGroup = @add.group()
+		@scoreText = @add.text 120, 20, "0", @fontStyle
 		@score = 0
 		@health = 100
 		#@mobs.spawnEnemy this

+	managePause: () ->
+		@game.paused = true
+		pausedText = @add.text 100, 250, "Game paused.\nClick anywhere to continue.", @fontStyle
+		@input.onDown.add(() ->
+			pausedText.destroy()
+			@game.paused = false
+		, this
+
+	update: () ->
+		@spawnTimer += @time.elapsed
+		if @spawnTimer > 1000
+			@spawnTimer = 0
+			#@items.spawnCandy this
+
+		@mobGroup.forEach (enemy) =>
+			enemy.angle += enemy.rotateMe
+
+		# game over
+		if @health <= 0
+			@add.sprite((Candy.GAME_WIDTH-594)/2, (Candy.GAME_HEIGHT-271)/2, 'game-over')
+			@game.paused = true
+		}
+	}
+
 module.exports = Game


handles the case where the user types "sudo" but without a command

--- src/scripts/sudo.coffee
+++ src/scripts/sudo.coffee
@@ -2,6 +2,6 @@
 #
 # hubot sudo <anything you want> - Force hubot to do what you want
 module.exports = (robot) ->
-  robot.respond /(sudo) (.*)/i, (msg) ->
-    msg.send "Alright. I'll #{msg.match[2]}"
+  robot.respond /(sudo)(.*)/i, (msg) ->
+    msg.send "Alright. I'll #{msg.match?[2] || "do whatever it is you wanted."}"


Better comments.

Better comments.

--- src/selix.coffee
+++ src/selix.coffee
@@ -74,6 +74,7 @@
   setText: setText
   wrap: wrap

+# Export to Node or window
 if typeof exports isnt 'undefined'
   module.exports = selix
 else if typeof window isnt 'undefined'


Automatically focus the provided input when required.

--- src/selix.coffee
+++ src/selix.coffee
@@ -4,6 +4,7 @@
   elem.value.slice(0, position).split('\r\n').length - 1

 getCaret = (elem) ->
+  elem.focus();
   if typeof elem.selectionStart is 'number' and typeof elem.selectionEnd is 'number'
     start: elem.selectionStart,
     end: elem.selectionEnd
@@ -35,6 +36,7 @@
     end: end

 setCaret = (elem, start, end = start) ->
+  elem.focus();
   if typeof elem.selectionStart is 'number' and typeof elem.selectionEnd is 'number'
     elem.selectionStart = start
     elem.selectionEnd = end


增加公共的css路径

增加公共的css路径

--- honey/index.coffee
+++ honey/index.coffee
@@ -62,17 +62,26 @@
   jsProduction = "http://js.hunantv.com/#{project_name}/"
   imgProduction = "http://img.hunantv.com/#{project_name}/"
   pubImgProduction = "#{imgProduction}imgotv-pub/"
-  pubFontProduct = "#{cssProduction}imgotv-pub/font/"
+  pubFontProduction = "#{cssProduction}imgotv-pub/font/"
+  pubCssProduction = "http://css.hunantv.com/imgotv-pub/"

   variables =
+    #公共的css路径
+    __pub_css: if isProduction then pubCssProduction else "/imgotv-pub/css/"
     #css的位置
     __css: if isProduction then cssProduction else "/css/"
+    #js路径
     __js: if isProduction then jsProduction else "/js/"
+    #图片路径
     __img: if isProduction then imgProduction else "/image/"
+    #公共模板
     __pub_tmpl: "/imgotv-pub/template/"
+    #项目名称
     __project: project_name
+    #公共库的图片
     __pub_img: if isProduction then pubImgProduction else "/imgotv-pub/image/"
-    __pub_font: if isProduction then pubFontProduct else "/font/"
+    #公共库的字体
+    __pub_font: if isProduction then pubFontProduction else "/font/"

   #把变量加到global中去
   jsonData = silky.data.json


#0 @xq 将通用项目的config设置配置到honey插件中

#0 @xq 将通用项目的config设置配置到honey插件中

--- honey/index.coffee
+++ honey/index.coffee
@@ -3,7 +3,6 @@
 _fs = require 'fs-extra'
 _url = require 'url'
 _path = require 'path'
-
 #_uglify = require 'uglify-js'

 ##压缩内联的js
@@ -105,9 +104,54 @@
   variables

 #更改config的配置
-changeConfig = (silky)->
+appendConfig = (silky)->
   #添加路由配置
-#  routers = silky.config.routers
+  routers = silky.config.routers
+  silky.config.routers = routers.concat(
+    #图片的转发
+    [
+      {path: /^.image.imgotv\-pub.(.+)$/i, to: '/imgotv-pub/image/$1', static: true, next: false}
+    ]
+  )
+
+  build = silky.config.build
+  #重命名
+  build.rename = build.rename.concat(
+    [
+      #公共图片
+      {source: /^imgotv\-pub.image.(.+)/i, target: '/image/imgotv-pub/$1', next: false}
+      #公共的CSS
+      {source: /^imgotv\-pub.css.(.+)/i, target: '/css/imgotv-pub/$1', next: false}
+      #公共的字体
+      {source: /^imgotv\-pub.font.(.+)/i, target: '/css/imgotv-pub/font/$1', next: false}
+      #重命名source的问题
+      {source: /source\.(js)$/i, target: '$1', next: false}
+    ]
+  )
+
+  #忽略掉的文件
+  build.ignore = build.ignore.concat(
+    [
+      #模板中的module
+      /^template.module$/i
+      #css中的module
+      /^css.module$/i
+      #以.开头的
+      /(^|\/)\.(.+)$/
+      #以log为扩展名的
+      /\.(log)$/i
+      #node_modules，预防性的
+      /^node_modules$/i
+      #公共库中css的include
+      /^imgotv\-pub.css.include$/i
+      #不编译公共库的模板
+      /^imgotv\-pub.template$/i
+    ]
+  )
+
+#  console.log build.ignore.length
+#  process.exit 0
+
 #  buildRename = silky.config.rename
 #  buildRename.push
 #    {
@@ -143,18 +187,23 @@
       @__pub_less_comp: '#{pubLess}component/';
   "

+
 #标识这是一个silky插件
 exports.silkyPlugin = true
 #提供注册插件的入口
 exports.registerPlugin = (silky, pluginOptions)->
   #在build和路由启动的时候，加入系统变量
-  silky.registerHook 'route:initial', -> appendSystemVariable silky
+  silky.registerHook 'route:initial', ->
+    appendSystemVariable silky
+    appendConfig silky
+
   silky.registerHook 'build:initial', ->
     #build时没有指定环境，则假定为preview环境
     #如果没有指定环境，则指定为preview环境，以解决上传到预览服务器的路径问题
     #在正式编译的时候，环境会被设置为production环境
     silky.options.env = 'preview' if not silky.options.original.env
     appendSystemVariable silky
+    appendConfig silky

   silky.registerHook 'route:willResponse', {}, (data, done)->
     url = _url.parse data.request.url


added images to grunt dist task

added images to grunt dist task

--- presentation/Gruntfile.coffee
+++ presentation/Gruntfile.coffee
@@ -70,7 +70,8 @@
                         'slides/**'
                         'bower_components/**'
                         'js/**'
-                        'resources/**'
+                        'resources/**',
+                        'images/**'
                     ]
                     dest: 'dist/'
                 },{


Respect sorted order of SetSort#merge

Respect sorted order of SetSort#merge

--- src/set/set_sort.coffee
+++ src/set/set_sort.coffee
@@ -23,6 +23,10 @@
     @base.registerAsMutableSource()
     for item in @get('_storage')
       return item if block(item)
+
+  merge: (other) ->
+    @base.registerAsMutableSource()
+    new Batman.Set(@_storage...).merge(other).sortedBy(@key, @order)

   compare: (a,b) ->
     return 0 if a is b


Updated to be compliant to Coffeelint

Updated to be compliant to Coffeelint

--- src/core/animationframe.coffee
+++ src/core/animationframe.coffee
@@ -6,7 +6,7 @@
     {requestAnimationFrame} = root

     unless requestAnimationFrame
-      for x in vendors
+      for x in vendors
         requestAnimationFrame = root["#{x}RequestAnimationFrame"]
         break if requestAnimationFrame

@@ -25,7 +25,8 @@

     unless cancelAnimationFrame
       for x in vendors
-        cancelAnimationFrame = root["#{x}CancelAnimationFrame"] or root["#{x}CancelRequestAnimationFrame"]
+        cancelAnimationFrame = root["#{x}CancelAnimationFrame"] or
+          root["#{x}CancelRequestAnimationFrame"]
         break if cancelAnimationFrame

     unless cancelAnimationFrame


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -16,7 +16,7 @@

   # Add events and methods here
   self.bind "update", ->
-    for
+    for property, data of

   # We must always return self as the last line
   return self


done! wow, this will never work!

done! wow, this will never work!


Revert "Replace validation to node built-in functions"

This reverts commit 9e4ff9f9d1dfa10098ec9fdbcd44420e2af64332.

--- src/utils.coffee
+++ src/utils.coffee
@@ -1,21 +1,24 @@
-util = require 'util'
+fun = (f) -> typeof f is 'function'
+str = (s) -> typeof s is 'string'
+arr = (a) -> a instanceof Array
+obj = (o) -> o instanceof Object and not fun(o) and not arr(o)

 exports.is =
-  fun: util.isFunction
-  str: util.isString
-  arr: util.isArray
-  obj: util.isObject
+  fun: fun
+  str: str
+  arr: arr
+  obj: obj

 exports.normalize = (params, options, fn) ->
-  if util.isFunction params
+  if fun params
     fn = params
     params = null
     options = null
-  else if util.isFunction options
+  else if fun options
     fn = options
     options = null

-  if not util.isFunction fn
+  if not fun fn
     fn = ->

   {params, options, fn}


fix critical bug in file checkbox range selection

--- salvus/page/project_store.coffee
+++ salvus/page/project_store.coffee
@@ -203,12 +203,19 @@
     set_current_path : (path) =>
         # Set the current path for this project. path is either a string or array of segments.
         p = @_project()
-        @setTo(current_path: path, page_number: 0)
+        @setTo
+            current_path           : path
+            page_number            : 0
+            most_recent_file_click : undefined
         @set_directory_files(path)
         @set_all_files_unchecked()

     set_file_search : (search) =>
-        @setTo(file_search : search, page_number : 0, file_action : undefined)
+        @setTo
+            file_search            : search
+            page_number            : 0
+            file_action            : undefined
+            most_recent_file_click : undefined

     # Update the directory listing cache for the given path
     set_directory_files : (path, sort_by_time, show_hidden) =>
@@ -263,19 +270,21 @@
             delete @_set_directory_files_lock[_key] # done!
         )

+    # Set the most recently clicked checkbox, expects a full/path/name
     set_most_recent_file_click : (file) ->
         @setTo(most_recent_file_click : file)

     # Set the selected state of all files between the most_recent_file_click and the given file
     set_selected_file_range : (file, checked) ->
-        most_recent = @get_store().state.most_recent_file_click
+        store = @get_store()
+        most_recent = store.state.most_recent_file_click
         if not most_recent?
             # nothing had been clicked before, treat as normal click
             range = [file]
         else
             # get the range of files
-            most_recent = @get_store().state.most_recent_file_click
-            names = (a.name for a in @get_store().get_displayed_listing().listing)
+            current_path = store.state.current_path
+            names = (misc.path_to_file(current_path, a.name) for a in store.get_displayed_listing().listing)
             range = misc.get_array_range(names, most_recent, file)

         if checked


set text content instead of html.

set text content instead of html.

--- resources/assets/coffee/store-username-change.coffee
+++ resources/assets/coffee/store-username-change.coffee
@@ -54,9 +54,9 @@
   preventUsernameSubmission()

   if requestedUsername.length == 0
-    $status.html 'Enter a username to check availability!'
+    $status.text 'Enter a username to check availability!'
   else
-    $status.html "Checking availability of #{requestedUsername}..."
+    $status.text "Checking availability of #{requestedUsername}..."
     debouncedCheckUsernameValidity()

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


Comments on server.coffee #61

Comments on server.coffee #61

--- server/src/server.coffee
+++ server/src/server.coffee
@@ -14,27 +14,49 @@

 Settings.DEBUG = debugmode

+# Initialise now.js and hook it up with the server
+#
+# This also means that the now.js javascript libraries don't need
+# to be manually included in the client code.
 everyone = nowjs.initialize(server)
+
+###*
+ * When someone connects, this method gets called.
+###
+everyone.on 'connect', ->
+  console.log "CONNECT: #{this.user.clientId}"
+
+
+###*
+ * Handles the event when anyone disconnects
+###
 everyone.on 'disconnect', ->
   console.log "DISCONNECT: #{this.user.clientId}"

   if(this.now.gameNumber?)
+    # The client that just disconnected is in a game.
     clientId = this.user.clientId
     gameNumber = this.now.gameNumber
+
+    # Get the game instance and the now.js group.
     {game, group} = getThisGameGroup(gameNumber)
-    groupReference = group.now
-    playerIndex = game.removeClient(clientId)
+
+    groupReference = group.now # Closure reference fix
+
+    playerIndex = game.removeClient(clientId) # Remove the player from the game
+
     game.nextTurn( ->
       groupReference.receiveMoveTimeUp()
       groupReference.receiveState(game.state))
+
+    # Tell everyone in the group that the player has disconnect
     group.now.receivePlayerDisconnect(playerIndex)
     group.now.receiveState(game.state)
+
+  # Remove empty games, and send everyone an update with the games.
   gamesManager.cleanGames()
   everyone.now.getGames()

-
-everyone.on 'connect', ->
-  console.log "CONNECT: #{this.user.clientId}"

 gamesManager = new GamesManager()
 if Settings.DEBUG
@@ -44,7 +66,7 @@

 ###*
  * Returns a pair {game,group} specifying the game and group for the given gameNumber
- * @param  {[type]} gameNumber [description]
+ * @param  {Number} gameNumber The game number to get
  * @return {Json} with game: {Game} the Game object for the game
  *                     group: {String} the string id nowjs uses for nowjs groups
 ###
@@ -54,12 +76,24 @@
   return {game: game, group: group}

+###*
+ * Creates a new game and tells everyone to update their game lists
+ * @param  {String} name       The name of the game
+ * @param  {Number} numPlayers The number of the players
+ * @param  {String} playerName The name of the first player (since they make the game)
+ * @param  {Number} numRounds  The number of rounds the game will have
+###
 everyone.now.createGame = (name, numPlayers, playerName, numRounds) ->
   gameNumber = gamesManager.newGame(name, numPlayers, numRounds)
   this.now.addClient(gameNumber, playerName)
   everyone.now.getGames()

-
+###*
+ * Adds a new player to the game, updates the new players game number in his now pocket
+ * and tells everyone to update their list of games.
+ * @param {Number} gameNumber The game number the player wants to join
+ * @param {String} playerName The name of the player to add.
+###
 everyone.now.addClient = (gameNumber, playerName) -> #called by client when connected
   {game, group} = getThisGameGroup(gameNumber)

@@ -88,7 +122,9 @@
     # else the game is already full, so tell him - tough luck

-
+###*
+ * When the client wants to get a list of games, this method is called.
+###
 everyone.now.getGames = ->
   gamesList = gamesManager.getGamesListJson()
   this.now.receiveGameList(gamesList)
@@ -96,9 +132,13 @@

-# client telling the server about the goal
-# this is when the goal setter sends his goal.
-# if the goal is valid, we tell everyone what the goal is.
+
+###*
+ * Client telling the server about the goal
+ * this is when the goal setter sends his goal.
+ * if the goal is valid, we tell everyone what the goal is.
+ * @param  {Number[]} goalArray The goal that the player has set
+###
 everyone.now.receiveGoal = (goalArray) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -116,15 +156,20 @@

-# put this in everyone's pocket. Server calls this when a player took too long.
+###*
+ * When the player takes too long, this method gets called.
+ * @param  {String} nextPlayerSocId  The socket id of the next player
+###
 everyone.now.moveTimeUp = (nextPlayerSocId) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   group.now.receiveState(game.state)
   this.now.receiveMoveTimeUp()

-# client telling the server that he wants to move a dice from unallocated to required
-# index = index to the unallocated array
+###*
+ * client telling the server that he wants to move a dice from unallocated to required
+ * @param  {Number} index index to the unallocated array
+###
 everyone.now.moveToRequired = (index) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -142,8 +187,11 @@
     this.now.badMove(e)
     console.log e

-# client telling the server that he wants to move a dice from unallocated to optional
-# index = index to the unallocated array
+
+###*
+ * client telling the server that he wants to move a dice from unallocated to optional
+ * @param  {Number} index index to the unallocated array
+###
 everyone.now.moveToOptional = (index) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -161,7 +209,10 @@
     this.now.badMove(e)
     console.log e

-# client telling the server that he wants to move a dice to forbidden
+###*
+ * client telling the server that he wants to move a dice from unallocated to Forbidden
+ * @param  {Number} index index to the unallocated array
+###
 everyone.now.moveToForbidden = (index) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -180,7 +231,9 @@
     console.log e

-# client telling the server that he wants to make a now challenge
+###*
+ * client telling the server that he wants to make a now challenge
+###
 everyone.now.nowChallengeRequest = () ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -192,7 +245,10 @@
   group.now.receiveNowChallengeDecideTurn(game.playerManager.getPlayerIdBySocket(this.user.clientId))
   group.now.receiveState(game.state)

-# client telling the server that he wants to make a never challenge
+
+###*
+ * client telling the server that he wants to make a never challenge
+###
 everyone.now.neverChallengeRequest = () ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -205,7 +261,10 @@
   group.now.receiveState(game.state)

-# client telling the server that he has made a decision about the challenge
+
+###*
+ * client telling the server that he has made a decision about the challenge
+###
 everyone.now.challengeDecision = (agree) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   groupReference = group.now
@@ -262,7 +321,9 @@

-# client telling the server that he wants to submit a solution
+###*
+ * client telling the server that he wants to submit a solution
+###
 everyone.now.challengeSolution = (answer) ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   try
@@ -291,8 +352,9 @@
     console.log e

-
-# client telling the server that he is ready for next round
+###*
+ * client telling the server that he is ready for next round
+###
 everyone.now.nextRoundReady = ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   game.readyForNextRound(this.user.clientId)
@@ -302,18 +364,29 @@
     group.now.receiveState(game.state)
     group.now.receiveGoalTurn(game.playerManager.players, game.globalDice, game.playerManager.getGoalSetterPlayerId(), Settings.goalSeconds)

+###*
+ * client telling the server that he wants to pause the turn timer
+###
 everyone.now.pauseTurnTimer = ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   game.pauseTurnTimer()
   group.now.receivePauseTurnTimer()
   group.now.receiveState(game.state)

+
+###*
+ * client telling the server that he wants to resume the turn timer
+###
 everyone.now.resumeTurnTimer = ->
   {game, group} = getThisGameGroup(this.now.gameNumber)
   game.resumeTurnTimer()
   group.now.receiveResumeTurnTimer()
   group.now.receiveState(game.state)

+
+###*
+ * client telling the server that he wants to skip the current players turn
+###
 everyone.now.skipTurn = ->
   console.log this.now.gameNumber
   {game, group} = getThisGameGroup(this.now.gameNumber)


Tests for up.render({ cache })

Tests for up.render({ cache })

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -2931,6 +2931,53 @@
           next =>
             expect(change1Error).toBeUndefined()
             expect(up.network.queue.allRequests.length).toEqual(2)
+
+      describe 'with { cache } option', ->
+
+        it 'reuses a cached request with { cache: true }', asyncSpec (next) ->
+          fixture('.element')
+
+          up.render('.element', url: '/path', cache: true)
+
+          next ->
+            expect(jasmine.Ajax.requests.count()).toBe(1)
+            expect(target: '.element', url: '/path').toBeCached()
+
+            up.render('.element', url: '/path', cache: true)
+
+          next ->
+            # See that the cached request is used.
+            expect(jasmine.Ajax.requests.count()).toBe(1)
+
+        it 'does not reuse a cached request with { cache: false }', asyncSpec (next) ->
+          fixture('.element')
+
+          up.render('.element', url: '/path', cache: false)
+
+          next ->
+            expect(jasmine.Ajax.requests.count()).toBe(1)
+            expect(target: '.element', url: '/path').not.toBeCached()
+
+            up.render('.element', url: '/path', cache: false)
+
+          next ->
+            # See that the cached request is used.
+            expect(jasmine.Ajax.requests.count()).toBe(2)
+
+        it 'does not reuse a cached request when no { cache } option is given', asyncSpec (next) ->
+          fixture('.element')
+
+          up.render('.element', url: '/path')
+
+          next ->
+            expect(jasmine.Ajax.requests.count()).toBe(1)
+            expect(target: '.element', url: '/path').not.toBeCached()
+
+            up.render('.element', url: '/path')
+
+          next ->
+            # See that the cached request is used.
+            expect(jasmine.Ajax.requests.count()).toBe(2)

       describe 'handling of [up-keep] elements', ->


trailing newline

trailing newline

--- src/kite-as-promised/kite.coffee
+++ src/kite-as-promised/kite.coffee
@@ -21,3 +21,4 @@
     new Promise (resolve) =>
       super resolve
     .nodeify callback
+


avoid double rendering on model changes

avoid double rendering on model changes

* don't bind to views which are already bound in a parent collection
  or if a parent view was bound there

--- lib/assets/javascripts/stark/model_binding.js.coffee
+++ lib/assets/javascripts/stark/model_binding.js.coffee
@@ -9,14 +9,12 @@
   bind_view: (view) ->
     # @log "binding view", view.constructor.name, "to model", @.constructor.name

+    # check if parent collection is already bound on this view
+    if @collection? && _.include @collection.bound_views, view
+      # @log "found our view already bound by our parent collection, bailing"
+      return

-    # TODO as with the below parent/child view checks, we should do the same
-    #      with collections and models. we will often have a case where a parent
-    #      view binds to the collection and it's child view binds to a model
-    #      contained in that collection.
-
-
-    # check to see if any of our parents (views) have already been bound here
+    # check to see if any of our parent views have already been bound here
     #
     # if they have, we don't want to bind again because when the parent
     # redraws, we will get redrawn anyway.
@@ -24,6 +22,9 @@
     while parent?
       if _.include @bound_views, parent
         # @log "found a parent view already bound, bailing"
+        return
+      if @collection? && _.include @collection.bound_views, parent
+        # @log "found a parent view already bound by our parent collection, bailing"
         return
       parent = parent.parent


debug~~~~

debug~~~~

--- lib/gitCreatePackage/gitCreatePackage-manager.coffee
+++ lib/gitCreatePackage/gitCreatePackage-manager.coffee
@@ -2,7 +2,7 @@
 fs = require 'fs-extra'
 os = require 'os'
 uuid = require 'uuid'
-
+request = require 'request'
 _ = require 'underscore'

 Q = require 'q'
@@ -69,23 +69,24 @@
         options =
           maxBuffer: 1024*1024*10
         gitPath = atom.config.get('atom-butterfly.gitCloneEnvironmentPath') # 设置git环境变量
-        options['env'] = path: gitPath if gitPath
+        options['env'] = path: gitPath if gitPath and gitPath != ''

         if obj.result # 创建仓库成功
           file = './lib/utils/gitApi_create.sh'
         else # 仓库已经存在
           file = './lib/utils/gitApi_update.sh'
-
+        file = "#{atom.getConfigDirPath()}/packages/atom-butterfly#{file}"
+
         notify stdout: "execFile: #{file} #{args.join(' ') if args}"
         cp = execFile file, args, options, (error, stdout, stderr) ->
-          if error then reject(error) else resolve()
+          if error then reject(error) else resolve(repoUrl)

-        cp.on 'exit', (code, signal)->
-          console.log "code:#{code}   signal： #{signal}"
-          if signal is 'SIGTERM' and code == null
-            reject new Error "SIGTERM"
-          else
-            resolve(repoUrl)
+        # cp.on 'exit', (code, signal)->
+        #   console.log "code:#{code}   signal： #{signal}"
+        #   if signal is 'SIGTERM' and code == null
+        #     reject new Error "SIGTERM"
+        #   else
+        #     resolve(repoUrl)

         cp.stdout.on 'data', (data) -> notify stdout: data.toString()
         cp.stderr.on 'data', (data) -> notify stderr: data.toString()


Make the ajax error handler work for all errors

--- js/inner.coffee
+++ js/inner.coffee
@@ -1,4 +1,10 @@
 window.observableCount = 0
+
+$(document).ajaxError (e, xhr, status, errorThrown) ->
+  if errorThrown
+    VM.setErrorMessage "Error: #{xhr.responseText}"
+  else
+    VM.setErrorMessage "An unknown error occurred: (#{xhr.status})."

 class Base
   constructor: (json, defaults={}, nonObservables=[], observe=true) ->
@@ -239,18 +245,12 @@
     @status() is 'followed'

   enable: =>
-    onerror = (xhr, status, errorThrown) =>
-      if errorThrown
-        VM.setErrorMessage "Error: #{xhr.responseText}"
-      else
-        VM.setErrorMessage "An unknown error occurred: (#{xhr.status})."
-
-    onsuccess = (data) =>
-      @status(data.status)
-
-    $.post("/api/v1/project/#{@project_name()}/enable")
-      .error(onerror)
-      .success(onsuccess)
+    $.post "/api/v1/project/#{@project_name()}/enable",
+      (data) =>
+        # Sometimes this returns {status: "available"} in development mode,
+        # because the keys are already installed in production, but aren't in
+        # the dev database. Solution: do nothing.
+        @status(data.status)


Make steps inherit from Obj rather than Base, setting the default to non-observable

--- js/inner.coffee
+++ js/inner.coffee
@@ -162,11 +162,14 @@
   appendLog: (json) =>
     @out.push(json.out)

-class Step extends Base
+class Step extends Obj
+
+  observables: =>
+    actions: []

   constructor: (json) ->
     json.actions = (new ActionLog(j) for j in json.actions) if json.actions
-    super json, {}, []
+    super json, {actions: []}

 #TODO: next step is to add the vcs_url, which is why I was looking at the knockout.model and knockout.mapping plugin
 class Build extends HasUrl


Call AtomEnvironment::deserialize in AtomEnvironment::loadStateSync

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -813,6 +813,9 @@
       catch error
         console.warn "Error parsing window state: #{statePath} #{error.stack}", error

+    @deserialize(state) if state?
+
+  deserialize: (state) ->
     @deserializeTimings.atom = Date.now() -  startTime

     if grammarOverridesByPath = state.grammars?.grammarOverridesByPath


Fix in query to contain quoting for each id.

--- src/coffee/apiclient.coffee
+++ src/coffee/apiclient.coffee
@@ -16,7 +16,7 @@
   getByExternalIds: (externalIds) ->
     @client.categories
     .all()
-    .query("externalId in #{externalIds.join(', ')}")
+    .where("externalId in (\"#{externalIds.join('", "')}\")")
     .fetch()

   update: (category, existingCategory, actionsToIgnore = [], context = {}) ->


Fix bug when decimalDigits is zero

Fix bug when decimalDigits is zero

--- src/coffee/vtex-i18n.coffee
+++ src/coffee/vtex-i18n.coffee
@@ -76,7 +76,7 @@
 		@currencyThousandsSeparator = thousandsSeparator

 	getDecimalDigits: (countryCodeParam) =>
-		return @currencyDecimalDigits if @currencyDecimalDigits
+		return @currencyDecimalDigits if @currencyDecimalDigits?
 		countryCode = if countryCodeParam then countryCodeParam else window.vtex.i18n.getCountryCode()
 		switch countryCode
 			when 'PRY'


Extend COMMAND authority

Extend COMMAND authority

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -9,10 +9,10 @@

 BUTTON_AUTHORITY = {
     fill: my.color.button
-    x: (world, args) -> world.index * my.button.spacing + my.margin
+    x: (world) -> world.index * my.button.spacing + my.margin
     y: my.margin
     height: my.button.size
-    width: my.button.size
+    width: (world) ->  world.get('height')
 }

 ROW_AUTHORITY = {
@@ -23,12 +23,14 @@
     width: (world) ->  world.up.get('width') - 2*my.margin
   }

-# TODO: Add ICON authority for smaller command display
-COMMAND_AUTHORITY = $.extend {
-  selected: (world) ->
+COMMAND_SIZE = my.button.size * 0.8
+COMMAND_SPACE = COMMAND_SIZE + my.margin / 2
+COMMAND_AUTHORITY = $.extend {}, BUTTON_AUTHORITY
+COMMAND_AUTHORITY['height'] = COMMAND_SIZE
+COMMAND_AUTHORITY['x'] = (world) -> world.index * COMMAND_SPACE + my.margin
+COMMAND_AUTHORITY['selected'] = (world) ->
     return false unless world.up.get('selected')
     world.index == world.get('current').get('programs').get('counter') + 1
-}, BUTTON_AUTHORITY # shallow copy

 display_commands = (name, programs) ->
   signals = programs.get('signals')


Test cursor position on change-to-end-of-line

an important distinction between D and C

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -364,6 +364,7 @@
         keydown('C', shift: true, element: editor[0])

         expect(editor.getText()).toBe "0\n"
+        expect(editor.getCursorScreenPosition()).toEqual([0,1])
         expect(editor).not.toHaveClass 'command-mode'
         expect(editor).toHaveClass 'insert-mode'


Handling only active editors.

Handling only active editors.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -74,6 +74,7 @@
     editorCount = 0
     nextKeys = _.clone keys
     atom.workspaceView.eachEditorView (e) ->
+        return if !e.active
         e.addClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
         e.find('.scroll-view .overlayer').append('

')
         positions = {}


More aggressive clearing on clicks and scrolls.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -93,12 +93,8 @@
             editorView.find '.scroll-view .overlayer'
                 .append "

"

-            activePane = editorView.getPane()
-            activePane.find '.vertical-scrollbar'
-                .on 'scroll', (e) ->
-                    that.clear()
             atom.workspaceView.find '*'
-                .on 'click', (e) ->
+                .on 'click scroll', (e) ->
                     that.clear()

             isScreenRowVisible = (lineNumber) ->


Don't append to existing search results.

--- src/views.coffee
+++ src/views.coffee
@@ -1451,8 +1451,8 @@

         update_results: ->
             @$el.show()
-            @category_collection.add @collection.where(object_type: 'service')
-            @service_point_collection.add @collection.where(object_type: 'unit')
+            @category_collection.set @collection.where(object_type: 'service')
+            @service_point_collection.set @collection.where(object_type: 'unit')

             if @category_collection.length
                 header_text = i18n.t('sidebar.search_category_count', {count: @category_collection.length})


Added .ld file support

Added .ld file support

--- lib/line-count.coffee
+++ lib/line-count.coffee
@@ -38,6 +38,7 @@
   "js"
   "jsx"
   "less"
+  "ld"
   "lua"
   "ls"
   "ml"


Keep track of log lines collection offset

--- SingularityUI/app/collections/LogLines.coffee
+++ SingularityUI/app/collections/LogLines.coffee
@@ -38,7 +38,7 @@
             error

     parse: (result) =>
-        offset = result.offset
+        @offset = offset = result.offset

         # split on newlines
         lines = result.data.split @delimiter


Fixed a small glitch

Fixed a small glitch

--- app/assets/javascripts/models/message.js.coffee
+++ app/assets/javascripts/models/message.js.coffee
@@ -15,6 +15,7 @@
   drops: -> new Cloudsdale.Collections.Drops(@get('drops'))

   user: (args) ->
+    author = if @get('author') then @get('author') else { id: @get('author_id') }
     return session.get('users').findOrInitialize @get('author'),
       fetch: false


toggle button text

toggle button text

--- app/assets/javascripts/review_history.js.coffee
+++ app/assets/javascripts/review_history.js.coffee
@@ -1,5 +1,14 @@
+toggleButtonText = (button_text, button_class) ->
+  parent = $('#' + button_class)
+  button = $('.toggle_history', parent)
+  if button_text == "Show"
+    button.text('Hide')
+  else
+    button.text('Show')
+
 $('document').ready ->
   $('tbody.review_history').hide()
   $('.toggle_history').on 'click', ->
     expandable_class = $(this).parent('td').attr('class')
     $('tbody.review_history.' + expandable_class).slideToggle('slow')
+    button_text = this.textContent.trim()


Speed up commonjs convert speed by 18 times.

--- build/convert_exports.coffee
+++ build/convert_exports.coffee
@@ -1,20 +1,30 @@
-fs = require('fs')
+fs = require 'fs'

 # Functional purity FTW.
+# Warning: this converter doesn't detect following case
+#   define ->
+#     a: a
+#     b: b
+# so you'll need to write module exports hashes in `{}`.

-String::startsWith = (subString) ->
-  this.lastIndexOf(subString, 0) is 0
+startsWith = (string, subString) ->
+  string.lastIndexOf(subString, 0) is 0

-String::repeat = (times) ->
-  Array(times + 1).join(this)
+repeat = (string, times) ->
+  Array(times + 1).join(string)

-createLineObject = (line, index) -> {line, index}
+TWO_SPACES = repeat ' ', 2
+FOUR_SPACES = repeat ' ', 4

-nonEmpty = (line) -> line.line isnt ''
+createLineObject = (line, index) ->
+  {line, index}
+
+nonEmpty = (line) ->
+  line.line isnt ''

 addTypes = (line) ->
-  if line.line.startsWith(' '.repeat(2))
-    if line.line.startsWith(' '.repeat(4)) or line.line.charAt(2) is '}'
+  if startsWith(line.line, TWO_SPACES)
+    if startsWith(line.line, FOUR_SPACES) or line.line.charAt(2) is '}'
       line.type = 'invalid'
     else
       line.type = 'indented'
@@ -22,7 +32,8 @@
     line.type = 'default'
   line

-nonInvalid = (line) -> line.type isnt 'invalid'
+valid = (line) ->
+  line.type isnt 'invalid'

 groupByNonIndented = (array, line) ->
   if line.type is 'indented'
@@ -31,7 +42,8 @@
     array.push []
   array

-nonNull = (variable) -> variable?
+defined = (variable) ->
+  variable?

 last = (array) ->
   array[array.length - 1]
@@ -40,19 +52,21 @@
   line.line = "  module.exports = #{line.line.trim()}"
   line

+replaceOriginalLine = (lines) -> (line) ->
+  lines[line.index] = line.line
+
 convertExports = (string) ->
   lines = string.split('\n')
   lines
     .map(createLineObject)
     .filter(nonEmpty)
     .map(addTypes)
-    .filter(nonInvalid)
+    .filter(valid)
     .reduce(groupByNonIndented, [])
     .map(last)
-    .filter(nonNull)
+    .filter(defined)
     .map(addModuleExports)
-    .forEach (line) ->
-      lines[line.index] = line.line
+    .forEach(replaceOriginalLine lines)
   lines.join('\n')

 testConvertExports = ->
@@ -81,8 +95,17 @@
       c2
     c1
     c-true
+
+  d
+    d2
+      d3
+    d5
+    {
+      d6
+    }
   """

   console.log convertExports test

+# testConvertExports()
 console.log convertExports fs.readFileSync('/dev/stdin').toString()


check if project exists in userCanEditDraft() first, to avoid harmless but annoying error

--- app/components/project-page/project-page.coffee
+++ app/components/project-page/project-page.coffee
@@ -55,6 +55,6 @@
       $location.path("/groups/#{groupId}/projects/#{projectId}/edit")

     $scope.userCanEditDraft = ->
-      $scope.project.status == 'draft' && $scope.userCanStartFunding()
+      $scope.project && $scope.project.status == 'draft' && $scope.userCanStartFunding()

     return


Pass showIndentGuides config to DisplayLayer

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -51,7 +51,7 @@
       @grammarRegistry, @packageManager, @assert
     })
     @buffer = @tokenizedBuffer.buffer
-    @displayLayer = @buffer.addDisplayLayer({tabLength: @getTabLength(), invisibles: @config.get('editor.invisibles')})
+    @displayLayer = @buffer.addDisplayLayer({tabLength: @getTabLength(), invisibles: @config.get('editor.invisibles'), showIndentGuides: @config.get('editor.showIndentGuide')})
     @displayLayer.setTextDecorationLayer(@tokenizedBuffer)
     @charWidthsByScope = {}
     @defaultMarkerLayer = @displayLayer.addMarkerLayer()


Added experimental goto feature

Added experimental goto feature

--- lib/pawn-build.coffee
+++ lib/pawn-build.coffee
@@ -85,6 +85,10 @@

       if data
         # show output of pawncc
+        editor = atom.workspace.getActiveTextEditor()
+        row = data.match(/\(([^)]+)\)/)[1]
+        if row
+          editor.setCursorBufferPosition([row,0])
         lines = data.split('\n')
         for line in lines
           output.add new PlainMessageView


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -6,7 +6,7 @@
     return memo if ran
     ran = true

-    return memo =
+    return memo = this.app

   _.once = function(func) {
     var ran = false, memo;


If braces in snippets are annoying

If braces in snippets are annoying

Signed-off-by: WebFreak001 <b0a51d41aaf8d5df1e850f1ef2ec58fb1c30319f@web.de>

--- snippets/language-d.cson
+++ snippets/language-d.cson
@@ -1,13 +1,13 @@
 ".source.d":
-	"if+condition":
+	"if":
 		"prefix": "if"
-		"body": "if($1) {$2}"
+		"body": "if($1)"
 	"else if":
 		"prefix": "elif"
-		"body": "else if($1) {$2}"
+		"body": "else if($1)"
 	"else+braces":
 		"prefix": "el"
-		"body": "else {$1}"
+		"body": "else"
 	"switch+case":
 		"prefix": "sw"
 		"body": """


Implement CursorComponent::shouldComponentUpdate

This prevents re-rendering cursors on blink, since only the class on
the outer component changes.

--- src/cursor-component.coffee
+++ src/cursor-component.coffee
@@ -1,5 +1,6 @@
 React = require 'react-atom-fork'
 {div} = require 'reactionary-atom-fork'
+{isEqualForProperties} = require 'underscore-plus'

 module.exports =
 CursorComponent = React.createClass
@@ -14,3 +15,6 @@
     WebkitTransform = "translate3d(#{left}px, #{top}px, 0px)"

     div className: 'cursor', style: {height, width, WebkitTransform}
+
+  shouldComponentUpdate: (prevProps) ->
+    not isEqualForProperties(prevProps, @props, 'pixelRect', 'scrollTop', 'scrollLeft', 'defaultCharWidth')


Adding missing preprocessing step when cache is used.

--- src/datasets/dataset.coffee
+++ src/datasets/dataset.coffee
@@ -80,7 +80,7 @@
             for interval in missingIntervals
                 @cache.reserve(interval...)
                 source(interval[0], interval[1], @sourceParams, (records) =>
-                    @cache.add(interval[0], interval[1], records)
+                    @cache.add(interval[0], interval[1], @postprocess(records))
                     @listeners.synced()
                     summaryCallback()
                 )


Added file extension for exports.

Added file extension for exports.

--- HTTPieCodeGenerator.coffee
+++ HTTPieCodeGenerator.coffee
@@ -137,8 +137,9 @@

 HTTPieCodeGenerator.identifier =
-    "com.luckymarmot.PawExtensions.HTTPieCodeGenerator";
+    "com.luckymarmot.PawExtensions.HTTPieCodeGenerator"
 HTTPieCodeGenerator.title =
-    "HTTPie";
+    "HTTPie"
+HTTPieCodeGenerator.fileExtension = "sh"

 registerCodeGenerator HTTPieCodeGenerator


Local port is now 2578

Local port is now 2578

--- src/flux/nylas-api.coffee
+++ src/flux/nylas-api.coffee
@@ -56,7 +56,7 @@

   constructor: ->
     @_lockTracker = new NylasAPIChangeLockTracker()
-    @LocalSyncRoot = 'http://localhost:5100'
+    @LocalSyncRoot = 'http://localhost:2578'

     env = NylasEnv.config.get('env')
     if env is 'local'


removing task plan exerciseIds and exercise_dynamic_count fro homeworks

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -7,6 +7,10 @@
   max: 4
   min: 2

+PLAN_TYPE =
+  homework: 'homework'
+  reading: 'reading'
+
 TaskPlanConfig =

   _stats: {}
@@ -16,8 +20,10 @@
     @_local[planId] ?= {}
     @_local[planId].settings ?= {}
     @_local[planId].settings.page_ids ?= []
-    @_local[planId].settings.exercise_ids ?= []
-    @_local[planId].settings.exercises_count_dynamic ?= TUTOR_SELECTIONS.default
+
+    if @_local[planId]?.type is PLAN_TYPE.homework or @_changed[planId]?.type is PLAN_TYPE.homework
+      @_local[planId].settings.exercise_ids ?= []
+      @_local[planId].settings.exercises_count_dynamic ?= TUTOR_SELECTIONS.default

     #TODO take out once TaskPlan api is in place
     _.extend({}, @_local[planId], @_changed[planId])
@@ -155,7 +161,7 @@

     isHomework: (id) ->
       plan = @_getPlan(id)
-      plan.type is 'homework'
+      plan.type is PLAN_TYPE.homework

     isValid: (id) ->
       plan = @_getPlan(id)


feat(web): reload app on logout;

feat(web): reload app on logout;

--- apps/actor-web/app/services/actorService.coffee
+++ apps/actor-web/app/services/actorService.coffee
@@ -28,6 +28,7 @@
     @isLoggedIn = false
     @$rootScope.$state.go 'login'
     @$rootScope.$broadcast 'actorLoggedOut'
+    location.reload()
   setCurrentPeer: (peer) ->
     console.log '[AW]ActorService setCurrentPeer'
     console.log '[AW]ActorService setCurrentPeer: peer:', peer


Added : Allow Cross Origin Requests

Added : Allow Cross Origin Requests

--- express/index.coffee
+++ express/index.coffee
@@ -11,6 +11,11 @@
         throw error

 app = express()
+# Allow Cross Origin
+app.use '/', (req, res, next) ->
+    res.header("Access-Control-Allow-Origin", "*")
+    res.header("Access-Control-Allow-Headers", "X-Requested-With")
+    next()

 app.configure 'production', ->
     app.use express.limit '5mb'


Include past shows in related shows on gallery pages

--- apps/show/components/related_shows/index.coffee
+++ apps/show/components/related_shows/index.coffee
@@ -25,7 +25,6 @@
     when 'gallery'
       data = _.extend criteria, {
         sort: '-start_at'
-        status: "upcoming"
       }
       relatedShows.url = "#{show.related().partner.url()}/shows"
       title = "Other Shows from #{show.partnerName()}"


Remove debugging statements.

Remove debugging statements.

--- services/web/public/coffee/ide/chat/controllers/ChatMessageController.coffee
+++ services/web/public/coffee/ide/chat/controllers/ChatMessageController.coffee
@@ -16,15 +16,9 @@
 				return ColorManager.getHueForUserId(user.id)

 		$scope.getMessageStyle = (user) ->
-			style =
-				"border-color"     : "hsl(#{ hue(user) }, #{ hslColorConfigs.borderSaturation }, #{ hslColorConfigs.borderLightness })"
-				"background-color" : "hsl(#{ hue(user) }, #{ hslColorConfigs.bgSaturation }, #{ hslColorConfigs.bgLightness })"
-			console.log style
-			return style
+			"border-color"     : "hsl(#{ hue(user) }, #{ hslColorConfigs.borderSaturation }, #{ hslColorConfigs.borderLightness })"
+			"background-color" : "hsl(#{ hue(user) }, #{ hslColorConfigs.bgSaturation }, #{ hslColorConfigs.bgLightness })"

 		$scope.getArrowStyle = (user) ->
-			style =
-				"border-color"     : "hsl(#{ hue(user) }, #{ hslColorConfigs.borderSaturation }, #{ hslColorConfigs.borderLightness })"
-			console.log style
-			return style
+			"border-color"     : "hsl(#{ hue(user) }, #{ hslColorConfigs.borderSaturation }, #{ hslColorConfigs.borderLightness })"
 	]


Use @related for logged errors.

Use @related for logged errors.

--- server/errors.coffee
+++ server/errors.coffee
@@ -30,70 +30,13 @@
     LoggedError.documents.insert errorDocument

 Meteor.publish 'logged-errors', ->
-  currentLoggedErrors = {}
-  currentPersonId = null # Just for asserts
-  handleLoggedErrors = null
-
-  removeLoggedErrors = =>
-    for id of currentLoggedErrors
-      delete currentLoggedErrors[id]
-      @removed 'LoggedErrors', id
-
-  publishLoggedErrors = =>
-    oldHandleLoggedErrors = handleLoggedErrors
-    handleLoggedErrors = LoggedError.documents.find(
-      {}
+  @related (person) ->
+    return unless person?.isAdmin
+    LoggedError.documents.find {}, LoggedError.PUBLIC_FIELDS()
+  ,
+    Person.documents.find
+      _id: @personId
     ,
-      LoggedError.PUBLIC_FIELDS()
-    ).observeChanges
-      added: (id, fields) =>
-        return if currentLoggedErrors[id]
-        currentLoggedErrors[id] = true
-
-        @added 'LoggedErrors', id, fields
-
-      changed: (id, fields) =>
-        return if not currentLoggedErrors[id]
-
-        @changed 'LoggedErrors', id, fields
-
-      removed: (id) =>
-        return if not currentLoggedErrors[id]
-        delete currentLoggedErrors[id]
-
-        @removed 'LoggedErrors', id
-
-    # We stop the handle after we established the new handle,
-    # so that any possible changes hapenning in the meantime
-    # were still processed by the old handle
-    oldHandleLoggedErrors.stop() if oldHandleLoggedErrors
-
-  handlePersons = Person.documents.find(
-    _id: @personId
-    isAdmin: true
-  ,
-    fields:
-      _id: 1 # We want only id
-  ).observeChanges
-    added: (id, fields) =>
-      # There should be only one person with the id at every given moment
-      assert.equal currentPersonId, null
-
-      currentPersonId = id
-      publishLoggedErrors()
-
-    removed: (id) =>
-      # We cannot remove the person if we never added the person before
-      assert.notEqual currentPersonId, null
-
-      handleLoggedErrors.stop() if handleLoggedErrors
-      handleLoggedErrors = null
-
-      currentPersonId = null
-      removeLoggedErrors()
-
-  @ready()
-
-  @onStop =>
-    handlePersons.stop() if handlePersons
-    handleLoggedErrors.stop() if handleLoggedErrors
+      fields:
+        # _id field is implicitly added
+        isAdmin: 1


Properly do not redirect inner api requests to https

--- server/server.coffee
+++ server/server.coffee
@@ -20,7 +20,8 @@

 requireHTTPS = (req, res, next) ->
     logger.info "secure=", req.secure, req.protocol, req.headers.host, req.url, req.headers["X-Forwarded-Proto"]
-    if !req.secure and req.headers.host != "127.0.0.1"  # the latter is to avoid inner api requests
+    if !req.secure and !req.headers.host.startsWith("127.0.0.1")  # the latter is to avoid inner api requests
+        logger.info "Redirecting to https"  req.protocol, req.headers.host, req.url, req.headers["X-Forwarded-Proto"]
         return res.redirect 'https://' + req.headers.host + req.url
     next()


Don't export sortLines

Don't export sortLines

--- lib/sort-lines.coffee
+++ lib/sort-lines.coffee
@@ -1,14 +1,14 @@
 RangeFinder = require './range-finder'

 module.exports =
-  activate: (state) ->
-    atom.workspaceView.command 'sort-lines:sort', '.editor', =>
+  activate: ->
+    atom.workspaceView.command 'sort-lines:sort', '.editor', ->
       editor = atom.workspaceView.getActivePaneItem()
-      @sortLines(editor)
+      sortLines(editor)

-  sortLines: (editor) ->
-    sortableRanges = RangeFinder.rangesFor(editor)
-    sortableRanges.forEach (range) ->
-      textLines = editor.getTextInBufferRange(range).split("\n")
-      textLines.sort (a, b) -> a.localeCompare(b)
-      editor.setTextInBufferRange(range, textLines.join("\n"))
+sortLines = (editor) ->
+  sortableRanges = RangeFinder.rangesFor(editor)
+  sortableRanges.forEach (range) ->
+    textLines = editor.getTextInBufferRange(range).split("\n")
+    textLines.sort (a, b) -> a.localeCompare(b)
+    editor.setTextInBufferRange(range, textLines.join("\n"))


Fix parsing of one-line messages

Fix parsing of one-line messages


Wrong prefix for browser

Wrong prefix for browser

--- src/RemoteSubGraph.coffee
+++ src/RemoteSubGraph.coffee
@@ -1,6 +1,6 @@
 noflo = require 'noflo'
 if noflo.isBrowser()
-  prefix = 'noflo-noflo-runtime/src/'
+  prefix = '/noflo-noflo-runtime/src/'
 else
   prefix = './'


col-md-push/pull for cart

col-md-push/pull for cart

--- public/views/widget_view.coffee
+++ public/views/widget_view.coffee
@@ -5,8 +5,8 @@

   template: _.template """

-

-

+

+


Refactor selection drag to unregister mouse events on drag end

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -7,6 +7,10 @@
 TableComponent = require './table-component'
 TableHeaderComponent = require './table-header-component'

+stopPropagationAndDefault = (f) -> (e) ->
+  e.stopPropagation()
+  e.preventDefault()
+  f?(e)

 module.exports =
 class TableView extends View
@@ -58,17 +62,11 @@
       'table-edit:select-to-end-of-table': => @expandSelectionToEndOfTable()
       'table-edit:select-to-beginning-of-table': => @expandSelectionToBeginningOfTable()

-    stopPropagationAndDefault = (f) -> (e) ->
-      e.stopPropagation()
-      e.preventDefault()
-      f?(e)
-
     @subscribeTo this,
       'mousedown': stopPropagationAndDefault (e) => @focus()

     @subscribeTo @head,
       'mousedown': stopPropagationAndDefault (e) =>
-
         if column = @columnAtScreenPosition(e.pageX, e.pageY)
           if column.name is @order
             if @direction is -1
@@ -89,18 +87,15 @@

         @startDrag(e)
         @focus()
-      'mousemove': stopPropagationAndDefault (e) => @drag(e)
-      'mouseup': stopPropagationAndDefault (e) => @endDrag(e)
       'click': stopPropagationAndDefault()

     @subscribeTo @body, '.table-edit-gutter',
       'mousedown': stopPropagationAndDefault (e) => @startGutterDrag(e)
-      'mousemove': stopPropagationAndDefault (e) => @gutterDrag(e)
-      'mouseup': stopPropagationAndDefault (e) => @endGutterDrag(e)
       'click': stopPropagationAndDefault()

     @subscribeTo @body, '.selection-box-handle',
       'mousedown': stopPropagationAndDefault (e) => @startDrag(e)
+      'click': stopPropagationAndDefault()

     @updateScreenRows()

@@ -149,9 +144,11 @@
   subscribeTo: (object, selector, events) ->
     [events, selector] = [selector, null] if typeof selector is 'object'
     if selector
-      object.on event, selector, callback for event, callback of events
-    else
-      object.on event, callback for event, callback of events
+      for event, callback of events
+        @subscriptions.add @asDisposable object.on event, selector, callback
+    else
+      for event, callback of events
+        @subscriptions.add @asDisposable object.on event, callback

   observeConfig: (configs) ->
     for config, callback of configs
@@ -792,6 +789,13 @@

     @dragging = true

+    @body.on 'mousemove', stopPropagationAndDefault (e) => @drag(e)
+    @body.on 'mouseup', stopPropagationAndDefault (e) => @endDrag(e)
+
+    @dragSubscription = new Disposable =>
+      @body.off 'mousemove'
+      @body.off 'mouseup'
+
   drag: (e) ->
     if @dragging
       {pageX, pageY} = e
@@ -829,11 +833,20 @@

     @drag(e)
     @dragging = false
+    @dragSubscription.dispose()

   startGutterDrag: (e) ->
     return if @dragging

     @dragging = true
+
+    @body.on 'mousemove', stopPropagationAndDefault (e) => @gutterDrag(e)
+    @body.on 'mouseup', stopPropagationAndDefault (e) => @endGutterDrag(e)
+
+    @dragSubscription = new Disposable =>
+      @body.off 'mousemove'
+      @body.off 'mouseup'
+
     row = @findRowAtScreenPosition(e.pageY)
     @setSelection(@getRowRange(row)) if row?

@@ -861,6 +874,7 @@
   endGutterDrag: (e) ->
     return unless @dragging

+    @dragSubscription.dispose()
     @gutterDrag(e)
     @dragging = false

@@ -936,3 +950,5 @@
     @hasChanged = false

   floatToPercent: (w) -> "#{Math.round(w * 10000) / 100}%"
+
+  asDisposable: (o) -> new Disposable -> o?.off()


Fix go-to-declaration for various cursor position within Ruby identifier

Even though technically the Ruby "word" can only start with a letter or
underscore, in practice it's not a good idea to define such regexp here,
because due to Atom's implementation of `editor.scanInBufferRange`, the
"word" under cursor will be looked up twice: once considering everything
up to the cursor position, and the second time considering everything
from the cursor position to the end of file.

So, if the cursor was positioned between "c" and "!" in `abc!`
identifier, the go-to-declaration for `abc!` symbol would fail.

--- lib/tag-reader.coffee
+++ lib/tag-reader.coffee
@@ -13,7 +13,7 @@
       cursor = editor.getLastCursor()
       scopes = cursor.getScopeDescriptor().getScopesArray()
       rubyScopes = scopes.filter (scope) -> /^source\.ruby($|\.)/.test(scope)
-      wordRegex = /[a-zA-Z_][\w!?]*/g if rubyScopes.length
+      wordRegex = /[\w!?]*/g if rubyScopes.length

       range = cursor.getCurrentWordBufferRange({wordRegex})
       symbol = editor.getTextInRange(range)


add SQLite test => JSON data can't be save

--- spec/c3store.spec.coffee
+++ spec/c3store.spec.coffee
@@ -5,25 +5,27 @@
 C3Store = require('../src/c3store') connect
 spec = require './store.spec.template'

-describe 'Sequelize session store implementation', ->
-  db = new Sequelize 'checkfoo', 'root', '', logging: false

-  # @todo write spec to use sqlite
-  # in Database#all('SELECT * FROM `Sessions` WHERE `sid`=\'sid1\' LIMIT 1;', [Function])
-  # => SyntaxError: Unexpected token \
-  #db = new Sequelize 'session-store', 'sa', 'secret',
-  #  logging: false,
-  #  dialect: 'sqlite'
-  #  storage: 'foo.db'
-  #  storage: ':memory:'
+sqlSpec = (db) ->
+#
+  describe 'Sequelize session store implementation', ->
+  #
+    spec (sessions) ->
+      store = new C3Store db, optional: type: Sequelize.STRING
+      sync = (done) -> store.SequelizeSession.sync(force: true).success(done)
+      aCheck 'sync model', sync, 50

-  spec (sessions) ->
-    store = new C3Store db, optional: type: Sequelize.STRING
-    sync = (done) -> store.SequelizeSession.sync(force: true).success(done)
-    aCheck 'sync model', sync, 50
+      for own sid of sessions
+        do (sid) -> aCheck 'save session', (done) ->
+          store.set sid, sessions[sid], done

-    for own sid of sessions
-      do (sid) -> aCheck 'save session', (done) ->
-        store.set sid, sessions[sid], done
+      store

-    store
+describe 'MySQL', -> sqlSpec new Sequelize 'checkfoo', 'root', '', logging: false
+xdescribe 'sqlite3', -> sqlSpec new Sequelize 'session-store', 'sa', 'secret',
+  #logging: false
+  dialect: 'sqlite'
+  storage: 'foo.db'
+#  storage: ':memory:'
+# in Database#all('SELECT * FROM `Sessions` WHERE `sid`=\'sid1\' LIMIT 1;', [Function])
+# => SyntaxError: Unexpected token \


fix context tests

fix context tests

--- spec/context.spec.coffee
+++ spec/context.spec.coffee
@@ -3,15 +3,12 @@
   context = require 'context'

   describe 'contexts', ->
-    $el = null
-    beforeEach ->
-      $el = $ '<div/>'
-
     it 'can be created', ->
-      context.create_run_context $el, []
+      context.create_run_context []

     it 'can output', ->
-      run_context = context.create_run_context $el, []
+      run_context = context.create_run_context []
       html = 'hello, world'
       run_context.output html
+      $el = context.render run_context
       expect($el.text()).toBe html


Made private variables more obvious.

Made private variables more obvious.


Enable click support on mobile devices.

Enable click support on mobile devices.

--- app/assets/javascripts/bsat/sidebar.js.coffee
+++ app/assets/javascripts/bsat/sidebar.js.coffee
@@ -23,9 +23,10 @@
   #
   # Use IScroll for scrolling the sidebar
   #
-  for selector in ['#sidebar']
-    new IScroll(selector, {
-      scrollbars: true,
-      fadeScrollbars: true,
-      mouseWheel: true
-    }) if $(selector)[0]
+  selector = '#sidebar'
+  new IScroll(selector, {
+    scrollbars: true,
+    fadeScrollbars: true,
+    mouseWheel: true,
+    click: true
+  }) if $(selector)[0]


Remove body as initialization option

Remove body as initialization option

--- app/assets/javascripts/dante/editor.js.coffee
+++ app/assets/javascripts/dante/editor.js.coffee
@@ -60,8 +60,6 @@
     @title              = title
     bodyplaceholder     = opts.body_placeholder   || 'Tell your story…'
     @body_placeholder   = "#{bodyplaceholder}
"
-    body                = opts.body               || ''
-    @body               = body
     embedplaceholder    = opts.embed_placeholder  || 'Paste a YouTube, Vine, Vimeo, or other video link, and press Enter'
     @embed_placeholder  = "#{embedplaceholder}
"
     extractplaceholder  = opts.extract_placeholder|| "Paste a link to embed content from another site (e.g. Twitter) and press Enter"
@@ -133,7 +131,7 @@

           #{if @disable_title then '' else @renderTitle()}
-
#{if @body.length > 0 then @body else @body_placeholder}

+
#{@body_placeholder}


:art: Rename item > classNames

:art: Rename item > classNames

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -401,8 +401,8 @@

       # Suppress activation if the editorView has classes that match the suppression list
       if shouldActivate
-        for item in @suppressForClasses
-          shouldActivate = false if _.intersection(@editorView.classList, item)?.length is item.length
+        for classNames in @suppressForClasses
+          shouldActivate = false if _.intersection(@editorView.classList, classNames)?.length is classNames.length

     if shouldActivate
       @cancelHideSuggestionListRequest()


Don't serialize a papers reviewers.

Don't serialize a papers reviewers.

These are saved via the reviewers task now.

--- app/assets/javascripts/models/paper.js.coffee
+++ app/assets/javascripts/models/paper.js.coffee
@@ -18,7 +18,7 @@

   availableReviewers: Ember.computed.alias('journal.reviewers')
   editor: Ember.computed.alias('editors.firstObject')
-  relationshipsToSerialize: ['reviewers']
+  relationshipsToSerialize: []

   displayTitle: (->
     @get('title') || @get('shortTitle')


Cleanup

Cleanup

--- app/coffee/modules/packages/core/debug.coffee
+++ app/coffee/modules/packages/core/debug.coffee
@@ -1,6 +1,6 @@
 exports.$service = [
-	'$rootScope', 'config'
-	($rootScope, config) ->
+	'$rootScope'
+	($rootScope) ->

 # Catch and log errors.


Set 'fromWhere' to the current route's name rather than the current URL

--- shared/router.coffee
+++ shared/router.coffee
@@ -102,4 +102,4 @@
 Router.onStop ->
   # If the route is an entry route, no need to save it
   if (!_.contains(exclusions, Router.current().route?.getName()))
-    Session.set('fromWhere', window.location.pathname)
+    Session.set('fromWhere', Router.current().route?.getName())


:guardsman: Protect marker destruction when not initialized

--- lib/color-marker-element.coffee
+++ lib/color-marker-element.coffee
@@ -38,7 +38,7 @@

   destroy: ->
     @parentNode?.removeChild(this)
-    @subscriptions.dispose()
+    @subscriptions?.dispose()
     @clear()

   render: ->


fix withConvertedUserAndStackTemplate

fix withConvertedUserAndStackTemplate

--- workers/social/testhelper/models/computeproviders/stacktemplatehelper.coffee
+++ workers/social/testhelper/models/computeproviders/stacktemplatehelper.coffee
@@ -1,4 +1,5 @@
 { _
+  expect
   withConvertedUser
   generateRandomString } = require '../../index'
 JStackTemplate = require '../../../../social/lib/social/models/computeproviders/stacktemplate'
@@ -37,7 +38,8 @@
   options            ?= {}

   withConvertedUser (data) ->
-    stackTemplateData = generateStackTemplateData data.client, options
+    { client }        = data
+    stackTemplateData = generateStackTemplateData client, options

     JStackTemplate.create client, stackTemplateData, (err, stackTemplate) ->
       expect(err).to.not.exist


Set CORS headers

Set CORS headers

--- storage/lib/rest.coffee
+++ storage/lib/rest.coffee
@@ -33,6 +33,11 @@
         res
             .status 403
             .send 'Valid API token required'
+
+    app.use (req, res, next) ->
+        res.set 'Access-Control-Allow-Origin', '*'
+        res.set 'Access-Control-Allow-Methods', 'GET, OPTIONS, HEAD'
+        next()

     app.get '/', (req, res, next) ->
         res.status 200


Atom plugin: correct snippets

Atom plugin: correct snippets

--- tools/atom-plugin/language-micro/snippets/language-micro.cson
+++ tools/atom-plugin/language-micro/snippets/language-micro.cson
@@ -1,16 +1,16 @@
 '.source.micro':
 	'function':
 		'prefix': 'fn'
-		'body': 'fn$1(${2:_})$3;'
-	'assignment':
-		'prefix': '='
-		'body': '= "$1" $2'
-	'assignment_to_parent':
-		'prefix': ':='
-		'body': ':= "$1" $2'
+		'body': 'fn$1($2)$3;$4'
 	'condition':
 		'prefix': 'if'
-		'body': 'if $1\n\tfn(_)$2;\'\n\tfn(_)$3;\'\nnil'
-	'loop':
-		'prefix': 'while'
-		'body': 'while fn(_) $1;\' fn(_)\n\t$2\n;\''
+		'body': 'if $1\n\tfn()$2;\n\tfn()$3;$4'
+	'pack':
+		'prefix': '>'
+		'body': '>@fn()$1;$2'
+	'unpack':
+		'prefix': '<'
+		'body': '<<@$1'
+	'list':
+		'prefix': ','
+		'body': ',$1$$2'


oops, add missing comma that breaks everything

--- client/vue/components.coffee
+++ client/vue/components.coffee
@@ -80,6 +80,6 @@
   GroupSettingCheckbox: 'group/setting_checkbox',
   DiscussionForkActions: 'discussion/fork_actions',
   MembershipRequestsCard: 'group/membership_requests_card',
-  PollCommonChooseType: 'poll/common/choose_type'
+  PollCommonChooseType: 'poll/common/choose_type',
   PollCommonFormActions: 'poll/common/form_actions'
 }


Update selected line when consolidating selections

--- lib/highlight-line-model.coffee
+++ lib/highlight-line-model.coffee
@@ -12,6 +12,7 @@
     @subscriptions.add(atom.workspace.observeTextEditors((activeEditor) =>
       activeEditor.onDidAddSelection(@updateSelectedLine)
       activeEditor.onDidChangeSelectionRange(@updateSelectedLine)
+      activeEditor.onDidRemoveSelection(@updateSelectedLine)
     ))
     @subscriptions.add(
       atom.workspace.onDidChangeActivePaneItem(@updateSelectedLine)


Remove deprecated swig file type

Remove deprecated swig file type

--- roles/developer-atom/templates/atom/init.coffee
+++ roles/developer-atom/templates/atom/init.coffee
@@ -11,14 +11,4 @@
 #     console.log "Saved! #{editor.getPath()}"
 {extname} = require 'path'

-fileTypes =
-  '.swig' : 'text.html.twig'
-
-atom.workspace.observeTextEditors (editor) ->
-  scopeName = fileTypes[extname editor.getPath()]
-  return unless scopeName?
-  g =  atom.grammars.grammarForScopeName scopeName
-  return unless g?
-  editor.setGrammar g
-
 process.env.PATH = ["/usr/local/bin", process.env.PATH].join(":")