Make tests loop over suite arrays, test log-levels

--- test/cli.coffee
+++ test/cli.coffee
@@ -9,8 +9,8 @@

 sampleProject = rel '../sample/project2'

-cliRun = (args, cwd) ->
-  cli = spawn command, args, {cwd}
+cliRun = (args) ->
+  cli = spawn command, args, {cwd: sampleProject}
   output = []
   ['stdout', 'stderr'].forEach (source) ->
     cli[source].setEncoding 'utf8'
@@ -19,55 +19,131 @@

   return {cli, output}

+removeTimestamps = (output) ->
+  # For each {source, message} in the collected output
+  output.map ({source, message}) ->
+
+    # If prefixed with a timestamp
+    if /\[.*\].*/.test(message)
+
+      # Slice off the timestamp prefix
+      message = message.slice(34)
+
+    return {source, message}
+
+tests = [
+  description: "should fail with a help message if missing 'moduleName'"
+  args: []
+  expected: [
+    source: 'stdout'
+    message: "Missing required positional argument: 'moduleName'\n"
+   ,
+    source: 'stdout'
+    message: 'Usage: axiom <moduleName> <serviceName> [<--arg> <value> ...]\n\n\n'
+  ]
+ ,
+  description: "should fail with a help message if missing 'serviceName'"
+  args: ['hello']
+  expected: [
+    source: 'stdout'
+    message: "Missing required positional argument: 'serviceName'\n"
+   ,
+    source: 'stdout'
+    message: 'Usage: axiom <moduleName> <serviceName> [<--arg> <value> ...]\n\n\n'
+  ]
+ ,
+  description: 'should start the server'
+  args: ['hello', 'world']
+  expected: [{source: 'stdout', message: 'Hello, world!\n'}]
+]
+
+logTests = [
+  description: "should accept '--log=info'"
+  args: ['hello', 'world', '--log=info']
+  expected: [
+    source: 'stdout'
+    message: "Calling 'core.init' with args: { config: undefined, retriever: undefined }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.load' with args: { moduleName: 'base' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.load' with args: { moduleName: 'hello' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.respond' with args: { channel: 'hello.world' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.request' with args: { channel: 'hello.world', data: { log: 'info' } }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.send' with args: { channel: 'hello.world', data: { log: 'info' } }\n"
+   ,
+    source: 'stdout'
+    message: 'Hello, world!\n'
+  ]
+ ,
+  description: "should accept '--log=debug'"
+  args: ['hello', 'world', '--log=debug']
+  expected: [
+    source: 'stdout'
+    message: "Calling 'core.listen' with args: { channel: 'axiom.log', topic: 'debug' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.init' with args: { config: undefined, retriever: undefined }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.load' with args: { moduleName: 'base' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.load' with args: { moduleName: 'hello' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.respond' with args: { channel: 'hello.world' }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.request' with args: { channel: 'hello.world', data: { log: 'debug' } }\n"
+   ,
+    source: 'stdout'
+    message: "Calling 'core.send' with args: { channel: 'hello.world', data: { log: 'debug' } }\n"
+   ,
+    source: 'stdout'
+    message: 'Hello, world!\n'
+  ]
+]
+
 describe 'cli', ->
   @timeout 2000

-  it "should fail with a help message if missing 'moduleName'", (done) ->
-    {cli, output} = cliRun []
+  # General tests
+  for test in tests
+    do (test) ->
+      # Given a set of arguments
+      {description, args, expected} = test

-    cli.on 'close', ->
-      output.should.eql [
-        source: 'stdout'
-        message: "Missing required positional argument: 'moduleName'\n"
-       ,
-        source: 'stdout'
-        message: 'Usage: axiom <moduleName> <serviceName> [<--arg> <value> ...]\n\n\n'
-      ]
-      done()
+      it description, (done) ->
+        # When we run the CLI with those arguments
+        {cli, output} = cliRun args

-  it "should fail with a help message if missing 'serviceName'", (done) ->
-    {cli, output} = cliRun ['hello']
+        cli.on 'close', ->
+          # Teh result should be what we expect
+          output.should.eql expected

-    cli.on 'close', ->
-      output.should.eql [
-        source: 'stdout'
-        message: "Missing required positional argument: 'serviceName'\n"
-       ,
-        source: 'stdout'
-        message: 'Usage: axiom <moduleName> <serviceName> [<--arg> <value> ...]\n\n\n'
-      ]
-      done()
+          done()

-  it 'should start server', (done) ->
-    args = ['hello', 'world']
-    cwd = rel '../sample/project2'
+  # Log-level tests
+  for test in logTests
+    do (test) ->
+      # Given an expected sequence of unprefixed log statements
+      {description, args, expected} = test

-    {cli, output} = cliRun args, cwd
+      it description, (done) ->
+        # When we run the CLI at a given log level
+        {cli, output} = cliRun args

-    cli.on 'close', ->
-      output.should.eql [
-        {source: 'stdout', message: 'Hello, world!\n'}
-      ]
-      done()
+        cli.on 'close', ->
+          # The result should be what we expect, after
+          # stripping the prefixed timestamps.
+          removeTimestamps(output).should.eql expected

-  it "should accept a '--debug' logging flag", (done) ->
-    true.should.be.false
-    done()
-
-  it "should accept an '--error' logging flag", (done) ->
-    true.should.be.false
-    done()
-
-  it "should accept an '--info' logging flag", (done) ->
-    true.should.be.false
-    done()
+          done()


Test MAM RSM

Test MAM RSM

--- test/mam.coffee
+++ test/mam.coffee
@@ -7,7 +7,7 @@

     it "must replay PubSub events to the sender", (done) ->
         @timeout 5000
-        mam_begin = new Date().toISOString() #TODO: timezone?
+        mam_begin = new Date().toISOString()

         async.series [(cb) ->
             # We need some data! So publish 2 posts, retract one, add an
@@ -212,4 +212,46 @@
         ], done

-    it.skip "must fail when there are too many results", (done) ->+    it "must fail when there are too many results", (done) ->
+        mam_begin = new Date(Date.now() - 60000).toISOString()
+
+        # Publish 2 posts, then MAM with a RSM max
+        async.series [(cb) ->
+            iqs = [
+                server.makePublishIq("picard@enterprise.sf", "buddycloud.example.org",
+                    "mam-D-1", "/user/picard@enterprise.sf/posts",
+                    content: "Test post D1", id: "mampost-D-1"),
+                server.makePublishIq("picard@enterprise.sf", "buddycloud.example.org",
+                    "mam-D-2", "/user/picard@enterprise.sf/posts",
+                    content: "Test post D2", id: "mampost-D-2"),
+            ]
+            async.forEachSeries iqs, (iq, cb2) ->
+                id = iq.root().attrs.id
+                server.doTest iq, "got-iq-#{id}", cb2, (iq) ->
+                    iq.attrs.should.have.property "type", "result"
+            , cb
+
+        , (cb) ->
+            iq = server.makeIq("get", "picard@enterprise.sf/abc", "buddycloud.example.org", "mam-D-3")
+                    .c("query", xmlns: NS.MAM)
+                    .c("start").t(mam_begin).up()
+                    .c("set", xmlns: NS.RSM)
+                    .c("max").t("1000")
+
+            server.doTest iq, "got-iq-mam-D-3", cb, (iq) ->
+                iq.attrs.should.have.property "type", "result"
+
+        , (cb) ->
+            iq = server.makeIq("get", "picard@enterprise.sf/abc", "buddycloud.example.org", "mam-D-4")
+                    .c("query", xmlns: NS.MAM)
+                    .c("start").t(mam_begin).up()
+                    .c("set", xmlns: NS.RSM)
+                    .c("max").t("1")
+
+            server.doTest iq, "got-iq-mam-D-4", cb, (iq) ->
+                iq.attrs.should.have.property "type", "error"
+                errEl = iq.getChild "error"
+                should.exist errEl, "missing element: <error/>"
+                errEl.attrs.should.have.property "type", "modify"
+                should.exist errEl.getChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas")
+        ], done


Bind it up in CoffeeScript

Bind it up in CoffeeScript

--- src/keyboard-layout.coffee
+++ src/keyboard-layout.coffee
@@ -7,6 +7,9 @@
 getCurrentKeyboardLayout = ->
   observer.getCurrentKeyboardLayout()

+getInstalledKeyboardLayouts = ->
+  observer.getInstalledKeyboardLayouts()
+
 onDidChangeCurrentKeyboardLayout = (callback) ->
   emitter.on 'did-change-current-keyboard-layout', callback

@@ -14,4 +17,4 @@
   callback(getCurrentKeyboardLayout())
   onDidChangeCurrentKeyboardLayout(callback)

-module.exports = {getCurrentKeyboardLayout, onDidChangeCurrentKeyboardLayout, observeCurrentKeyboardLayout}
+module.exports = {getCurrentKeyboardLayout, getInstalledKeyboardLayouts, onDidChangeCurrentKeyboardLayout, observeCurrentKeyboardLayout}


fix(container): added first_class check to init_single_containers

--- src/lib/containable.coffee
+++ src/lib/containable.coffee
@@ -7,8 +7,9 @@

     Observable self, name, new container_fn(data)

-    for editable in self[name]().editables
-      MappedObservable self, editable, self[name]()
+    if first_class?
+      for editable in self[name]().editables
+        MappedObservable self, editable, self[name]()

     self[name]().__updated.subscribe (data) =>
       callback = DelayedSave.apply(self, [options, self, datahandler])


Send response instead of reply

Send response instead of reply

--- scripts/clojure.coffee
+++ scripts/clojure.coffee
@@ -34,6 +34,6 @@
             else
               outputs = result.result.split("\n")
               for output in outputs
-                msg.reply output
+                msg.send output
           else
             msg.reply "Unable to evaluate script: #{script}. Request returned with the status code: #{res.statusCode}"


Implement docker based latex compilation.

Implement a docker based command runner that runs compilation in a docker container, for added isolation and security.

This implementation also supports timeouts.

--- app/coffee/DockerRunner.coffee
+++ app/coffee/DockerRunner.coffee
@@ -1,10 +1,12 @@
 spawn = require("child_process").spawn
 exec  = require("child_process").exec
 logger = require "logger-sharelatex"
+Settings = require "settings-sharelatex"
+

 module.exports = DockerRunner =
   _docker: 'docker'
-  _baseCommand: ['run', '--rm=true', '-t', '-v', '$COMPILE_DIR:/source', '--name=texlive-$PROJECT_ID', 'texlive']
+  _baseCommand: ['run', '--rm=true', '-t', '-v', '$COMPILE_DIR:/source', '--name=texlive-$PROJECT_ID']

   run: (project_id, command, directory, timeout, callback = (error) ->) ->
     logger.log project_id: project_id, command: command, directory: directory, "running docker command"
@@ -14,13 +16,14 @@

     docker_cmd = (arg.replace('$COMPILE_DIR', directory).replace('$PROJECT_ID', project_id) \
       for arg in DockerRunner._baseCommand)
+    docker_cmd.push Settings.clsi?.docker?.image or "texlive"
     docker_cmd = docker_cmd.concat (arg.replace('$COMPILE_DIR', '/source') for arg in command.slice(1))

     proc = spawn DockerRunner._docker, docker_cmd, stdio: "inherit", cwd: directory
     timer = setTimeout timeout, () ->
       logger.warn "timeout achieved, stopping docker instance"
       exec 'docker', ['stop', "texlive-#{project_id}"]
-      callback 'Compilation failed, timeout'
+      callback {timedout: true}
     proc.on "close", () ->
       clearTimeout timer
       callback()


Catch more errors after login call

Catch more errors after login call

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -169,10 +169,9 @@
                 $location.path("/project/project-example-0/backlog")

             promise.then null, (response) ->
-                if response.data._error_message
-                    $confirm.notify("light-error", "According to our Oompa Loompas,
-                                                    your username/email or password
-                                                    are incorrect.") #TODO: i18n
+                $confirm.notify("light-error", "According to our Oompa Loompas,
+                                                your username/email or password
+                                                are incorrect.") #TODO: i18n

         $el.on "submit", (event) ->
             event.preventDefault()


Add feature to mark a greyhound as no longer pending adoption

--- scripts/pending.coffee
+++ scripts/pending.coffee
@@ -2,7 +2,7 @@
 #   Label a greyhound as pending adoption
 #
 # Commands:
-#   hubot pending <greyhound> - Labels a greyhound as pending adoption
+#   hubot pending <greyhound> (yes/no) - Labels a greyhound as pending adoption
 #
 # Author:
 #   Zach Whaley (zachwhaley) <zachbwhaley@gmail.com>
@@ -12,7 +12,13 @@
 git = require '../lib/git'
 site = require '../lib/site'

-pending = (greyhound, callback) ->
+pendingBranch = (pending) ->
+  return if pending then "pending" else "not-pending"
+
+pendingMessage = (pending) ->
+  return if pending then "Pending Adoption! 🎉" else "Not Pending Adoption 🤷"
+
+pending = (greyhound, pending, callback) ->
   site.loadGreyhound greyhound, (info, bio) ->
     if not info?
       return callback "Sorry, couldn't find #{greyhound} 😕"
@@ -21,24 +27,31 @@
       return callback "#{capitalize(greyhound)} has crossed the Rainbow Bridge 😢"
     if info.category is 'adopted'
       return callback "#{capitalize(greyhound)} has already been adopted 😝"
-    if info.pending is yes
+    if pending and info.pending is yes
       return callback "#{capitalize(greyhound)} is already pending adoption 😝"
+    if not pending and info.pending is no
+      return callback "#{capitalize(greyhound)} is already not pending adoption 😝"

-    info.pending = yes
+    info.pending = pending
     site.dumpGreyhound greyhound, info, bio, callback

 module.exports = (robot) ->
-  robot.respond /pending (.*)/i, (res) ->
+  robot.respond /pending (\w+)\s?(\w+)?/i, (res) ->
     greyhound = res.match[1]?.toLowerCase()
+    if res.match[2]?
+      pend = if res.match[2].toLowerCase() is 'no' then no else yes
+    else
+      pend = yes
+
     gitOpts =
-      message: "#{capitalize(greyhound)} Pending Adoption! 🎉"
-      branch: "pending-#{greyhound}"
+      message: "#{capitalize(greyhound)} #{pendingMessage(pend)}"
+      branch: "#{pendingBranch(pend)}-#{greyhound}"
       user:
         name: res.message.user?.real_name
         email: res.message.user?.profile?.email

-    res.reply "Labeling #{capitalize(greyhound)} as Pending Adoption! 🎉\n" +
+    res.reply "Labeling #{capitalize(greyhound)} as #{pendingMessage(pend)}\n" +
               "Hang on a sec..."

-    git.update pending, greyhound, gitOpts, (update) ->
+    git.update pending, greyhound, pend, gitOpts, (update) ->
       res.reply update


Push users into array of entrants.

Push users into array of entrants.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -44,11 +44,11 @@
     robot.respond /ticket$/i, (msg) ->
         username = "#{msg.envelope.user.name}"
         points[username] ?= 0
-        entrants = flatten([entered[username]])

         if points[username] >= cost and raffle is on and entered[username] is false
             entered[username] = true
             points[username] -= cost
+            entrants.push(entered[username])
             save(robot)
             msg.send "Test: raffle entered."
         else


weather script

weather script

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -5,6 +5,7 @@

 module.exports = (robot) ->
   robot.respond /weather (.*)/i, (msg) ->
+    locationOrig = msg.match[1]
     location = escape(msg.match[1]) or 'Kansas City'
     locationOptions =
       uri: 'http://maps.googleapis.com/maps/api/geocode/json'
@@ -46,10 +47,9 @@
               msg.send('No weather today. Stay indoors and don\'t look outside.')
             )
         else
-          if location == 'my ass'
-            msg.send('It\'s hot and moist today.')
+          if locationOrig == 'my ass'
+            msg.send('It\'s hot and moist out today.')
           else
-            console.log(location)
             msg.send('Nope. Sorry. Not happening.')
     )
     .catch( (err) ->


added description of Media and Visibility pages

--- app/pages/lab/how-to-page.cjsx
+++ app/pages/lab/how-to-page.cjsx
@@ -223,6 +223,18 @@

       _Education_: If you are a researcher open to collaborating with educators you can state that here, include educational content, and describe how you'd like to help educators use your project. Also, if your project is primarily for educational purposes you can describe that here. This page will only display if you add content to it.

+      ### DETAILS - Media
+
+      You can upload your own media to your project (such as example images for your help pages) so  you can link to it without an external host. To start uploading, drop an image into the box (or click it to bring up your file browser and select a file).
+
+      Once the image has uploaded, it will appear above the "Add an image" box. You can then copy the markdown text beneath the image into your project, or add another image.
+
+      Currently there is a limit of 20 uploaded images per project.
+
+      ### DETAILS - Visibility
+
+      This page is where you decide whether your project is public and whether it's ready to go live. For more information on the different project stages, see our [project builder policies](#/lab-policies).
+
       ### DETAILS - Collaborators

       Add people to your team and specify what their roles are so that they have the right access to the tools they need (including access to the project before it's public).
@@ -258,4 +270,4 @@
   render: ->
     <div className="secondary-page centered-grid">
       <Markdown>{counterpart "howToPage.content"}</Markdown>
-    </div>+    </div>


add private field info to subj uploader

add private field info to subj uploader

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -155,6 +155,7 @@
         <UploadDropTarget accept="text/csv, text/tab-separated-values, image/*" multiple onSelect={@handleFileSelection}>
           Drag-and-drop or click to upload manifests and subject images here.

           Manifests must be .csv or .tsv. The first row should define metadata headers. All other rows should include at least one reference to an image filename in the same directory as the manifest.

+          Headers that begin with "#" or "//" denote private fields that will not be visible to classifiers.

           Subject images can be up to {MAX_FILE_SIZE/1000}KB and any of: {{ext}{', ' if VALID_SUBJECT_EXTENSIONS[i + 1]?} for ext, i in VALID_SUBJECT_EXTENSIONS}{' '}
           and may not contain {{char}{', ' if INVALID_FILENAME_CHARS[i + 1]?} for char, i in INVALID_FILENAME_CHARS}

         </UploadDropTarget>


remove promise-renderer from SubjectSetListing

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -1,7 +1,6 @@
 React = require 'react'
 AutoSave = require '../../components/auto-save'
 handleInputChange = require '../../lib/handle-input-change'
-PromiseRenderer = require '../../components/promise-renderer'
 apiClient = require 'panoptes-client/lib/api-client'
 ChangeListener = require '../../components/change-listener'
 Papa = require 'papaparse'
@@ -72,11 +71,19 @@
   getInitialState: ->
     page: 1
     pageCount: NaN
-
-  render: ->
+    subjects: null
+
+  componentWillReceiveProps: (nextProps) ->
+    if @props.subjectSet.id isnt nextProps.subjectSet.id
+      @setSubjectResources(nextProps.subjectSet.id, "1")
+
+  componentWillMount: ->
+    @setSubjectResources(@props.subjectSet.id, @state.page)
+
+  setSubjectResources: (subjetSetId, page) ->
     gettingSetMemberSubjects = apiClient.type('set_member_subjects').get
-      subject_set_id: @props.subjectSet.id
-      page: @state.page
+      subject_set_id: subjetSetId
+      page: page

     gettingSetMemberSubjects.then ([setMemberSubject]) =>
       newPageCount = setMemberSubject?.getMeta().page_count
@@ -84,21 +91,34 @@
         @setState pageCount: newPageCount

     gettingSubjects = gettingSetMemberSubjects.get 'subject'
-
-

-      <PromiseRenderer promise={gettingSubjects} then={(subjects) =>
-        <SubjectSetListingTable subjects={subjects} onPreview={@previewSubject} onRemove={@removeSubject} />
-      } />
-
-        Page <select value={@state.page} disabled={@state.pageCount < 2 or isNaN @state.pageCount} onChange={(e) => @setState page: e.target.value}>
-          {if isNaN @state.pageCount
-            <option>?</option>
-          else
-            for p in [1..@state.pageCount]
-              <option key={p} value={p}>{p}</option>}
-        </select> of {@state.pageCount || '?'}
-
-

+    gettingSubjects.then( (subjects) =>
+      @setState
+        subjects: subjects
+        page: page
+    ).catch( (error) =>
+      console.log error
+    )
+
+  newPage: (e) ->
+    pageValue = e.target.value
+    @setSubjectResources @props.subjectSet.id, pageValue
+
+  render: ->
+    if @state.subjects
+

+        <SubjectSetListingTable subjects={@state.subjects} onPreview={@previewSubject} onRemove={@removeSubject} />
+
+          Page <select value={@state.page} disabled={@state.pageCount < 2 or isNaN @state.pageCount} onChange={(e) => @newPage(e)}>
+            {if isNaN @state.pageCount
+              <option>?</option>
+            else
+              for p in [1..@state.pageCount]
+                <option key={p} value={p}>{p}</option>}
+          </select> of {@state.pageCount || '?'}
+
+

+    else
+      null

   previewSubject: (subject) ->
     alert

@@ -338,10 +358,8 @@
     if nextProps.params.subjectSetID isnt @props.params.subjectSetID
       @setSubjectSet(nextProps.params.subjectSetID)

-
   componentWillMount: ->
     @setSubjectSet(@props.params.subjectSetID)
-

   setSubjectSet: (subjectSetId) ->
     subject_set = apiClient.type('subject_sets').get subjectSetId


added board logging

added board logging

--- app/scripts/chess/board.coffee
+++ app/scripts/chess/board.coffee
@@ -2,6 +2,10 @@

 # eventually Piece should have a canMove
 class Piece
+	constructor: (@isWhite) ->
+		@color = if @isWhite then 'white' else 'black'
+		@symbol = if @isWhite then @whiteSymbol else @blackSymbol
+
 	type: ->
 		@name

@@ -11,30 +15,44 @@
 class BlankPiece extends Piece
 	name: ""
 	value: 0
+	blackSymbol: "\u25A0"
+	whiteSymbol: "\u25A1"

 class Pawn extends Piece
 	name: "Pawn"
 	value: 1
+	blackSymbol: "\u265F"
+	whiteSymbol: "\u2659"
+
+class Knight extends Piece
+	name: "Knight"
+	value: 3
+	blackSymbol: "\u265E"
+	whiteSymbol: "\u2658"

 class Bishop extends Piece
 	name: "Bishop"
 	value: 3
-
-class Knight extends Piece
-	name: "Knight"
-	value: 3
+	blackSymbol: "\u265D"
+	whiteSymbol: "\u2657"

 class Rook extends Piece
 	name: "Rook"
 	value: 5
+	blackSymbol: "\u265C"
+	whiteSymbol: "\u2656"

 class Queen extends Piece
 	name: "Queen"
 	value: 9
+	blackSymbol: "\u265B"
+	whiteSymbol: "\u2655"

 class King extends Piece
 	name: "King"
 	value: 137
+	blackSymbol: "\u265A"
+	whiteSymbol: "\u2654"

@@ -58,9 +76,10 @@
 class Board
 	# fill in with peices
 	board =
-	for x in [1..8]
-		for y in [1..8]
-			new BlankPiece
+	for x in [0..7]
+		for y in [0..7]
+			# if x+y % 2 == 0 then it's a white square
+			new BlankPiece (x + y) % 2 == 0

 	# these work with either algebraic notation or x,y coordinates
 	set: (pos..., to) ->
@@ -82,6 +101,18 @@

 		return board[x][y]

+	visual: ->
+		for x in [0..7]
+			for y in [0..7]
+				@get(x,y).symbol
+
+	# draw the board to the console
+	log: ->
+		console.log "Current Board:"
+		vis = @visual()
+		for piece in vis
+			console.log piece.join(' ')
+

@@ -91,10 +122,20 @@
 #console.log xyToan 1, 8

 myBoard = new Board
-myBoard.set("a2", new Knight)
-myBoard.set(1,1, new King)
-console.log myBoard.get(1,2)
-console.log myBoard.get("a1")
+myBoard.set("a2", new Knight true)
+myBoard.set(1,1, new Knight false)
+
+myBoard.log()
+
+#console.log myBoard.get(1,2)
+#console.log myBoard.get("a1")
+#
+#console.log myBoard.get(1,2).color
+#console.log myBoard.get(1,1).color
+#
+#console.log myBoard.get(1,2).symbol
+#console.log myBoard.get(1,1).symbol
+

 #myPiece = new Pawn
 #console.log myPiece.pointValue()


Don't activate data-turbolinks-eval=false scripts

--- src/turbolinks/renderer.coffee
+++ src/turbolinks/renderer.coffee
@@ -14,10 +14,13 @@
     @delegate.viewInvalidated()

   cloneScriptElement: (element) ->
-    activatedScriptElement = document.createElement("script")
-    activatedScriptElement.textContent = element.textContent
-    copyElementAttributes(activatedScriptElement, element)
-    activatedScriptElement
+    if element.getAttribute("data-turbolinks-eval") is "false"
+      element.cloneNode(true)
+    else
+      clonedScriptElement = document.createElement("script")
+      clonedScriptElement.textContent = element.textContent
+      copyElementAttributes(clonedScriptElement, element)
+      clonedScriptElement

   copyElementAttributes = (destinationElement, sourceElement) ->
     for {name, value} in sourceElement.attributes


Improved reference to data and scaling in compare_bar_chart directive.

--- app/scripts/directives/compare-bar-chart.coffee
+++ app/scripts/directives/compare-bar-chart.coffee
@@ -12,20 +12,19 @@
       scope.$watch( (()-> angular.element(window)[0].innerWidth), ((newValue, oldValue)-> scope.drawBarChart()) )
       window.onresize = (()-> scope.$apply())

-      svg = d3.select(element[0]).append("svg")
-        .attr('class', 'bar-holder')
-        .style('fill', '#ddd')
-        .attr('width', "100%") # arbitrary
-        .attr('height', 100) # arbitrary
-
       scope.drawBarChart = () ->
-        raw_data = JSON.parse(d3.select(element[0]).attr('data'))
-        this_data = [-raw_data[0], raw_data[1]]
+        this_data = [-scope.data[0], scope.data[1]]
         x0 = Math.max(-d3.min(this_data), d3.max(this_data))
+
+        svg = d3.select(element[0]).append("svg")
+          .attr('class', 'bar-holder')
+          .style('fill', '#ddd')
+          .attr('width', "100%") # arbitrary
+          .attr('height', 100) # arbitrary

         x = d3.scale.linear()
           .domain([-x0, x0])
-          .range([0 , 900]) #arbitrary
+          .range([0 , "100%"])
           .nice()

         svg.selectAll("rect")
@@ -40,9 +39,7 @@
           .transition()
             .duration(1000)
             .attr("width", (d, i) ->
-              return Math.abs(x(d) - x(0))
+              return Math.abs(parseInt(x(d)) - parseInt(x(0))) + "%"
             ).attr("x", (d, i) -> return x(Math.min(0, d)))

-      scope.drawBarChart()
-
   ]


Add todos.

Add todos.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -243,5 +243,11 @@
             relevantLabels = textEditorElement.querySelectorAll('.label:not(.irrelevant)')
             expect(relevantLabels.length > 0).toBeTruthy()

+    # TODO: IMPLEMENT THIS.  Line 192 `pane.activate()` in jumpy-view.coffee
+    # should be enough to make this red to green.
     describe "when jumpy jumps to another pane", ->
         it "focuses the new pane", ->
+
+    # TODO: IMPLEMENT THIS.
+    describe "when jumpy jumps", ->
+        it "clears hotkeys", ->


Restoring fold test with foldCurrentRow().

- This completes the code folding branch.
- Fixes #28.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -91,16 +91,18 @@
             expect(cursorPosition.row).toBe 0
             expect(cursorPosition.column).toBe 6
         it "jumps the cursor in folded regions", ->
-            editor.setCursorBufferPosition [1, 1]
-            editor.foldBufferRow(24)
+            editorView.trigger 'jumpy:clear'
+            editor.setCursorBufferPosition [23, 20]
+            editor.foldCurrentRow()
+            editorView.trigger 'jumpy:toggle'
             editorView.trigger 'jumpy:d'
             editorView.trigger 'jumpy:i'
-            cursorPosition = editor.getCursorBufferPosition()
-            expect(cursorPosition.row).toBe 22
+            cursorPosition = editor.getCursorScreenPosition()
+            expect(cursorPosition.row).toBe 23
             expect(cursorPosition.column).toBe 2
             editorView.trigger 'jumpy:d'
             editorView.trigger 'jumpy:h'
-            cursorPosition = editor.getCursorBufferPosition()
+            cursorPosition = editor.getCursorScreenPosition()
             expect(cursorPosition.row).toBe 22
             expect(cursorPosition.column).toBe 0


Corrections to spec

Corrections to spec

--- spec/point-spec.coffee
+++ spec/point-spec.coffee
@@ -10,8 +10,8 @@

   describe "::fromObject(object, copy)", ->
     it "returns a new Point if object is point-compatible array ", ->
-      expect(Point.fromObject([1, 3]).isEqual(Point(1, 3)))
-      expect(Point.fromObject([Infinity, Infinity]).isEqual(Point.infinity()))
+      expect(Point.fromObject([1, 3])).toEqual Point(1, 3)
+      expect(Point.fromObject([Infinity, Infinity])).toEqual Point.infinity()

     it "returns the copy of object if it is an instanceof Point", ->
       origin = Point(0, 0)
@@ -20,32 +20,32 @@

   describe "::copy()", ->
     it "returns a copy of the object", ->
-      expect(Point(3, 4).copy().isEqual(Point(3, 4)))
-      expect(Point.zero().copy().isEqual([0, 0]))
+      expect(Point(3, 4).copy()).toEqual Point(3, 4)
+      expect(Point.zero().copy()).toEqual [0, 0]

   describe "::negate()", ->
     it "returns a new point with row and column negated", ->
-      expect(Point(3, 4).negate().isEqual(Point(-3, -4)))
-      expect(Point.zero().negate().isEqual(Point.zero()))
+      expect(Point(3, 4).negate()).toEqual Point(-3, -4)
+      expect(Point.zero().negate()).toEqual [0, 0]

   describe "::freeze()", ->
-    it "returns the immutable copy of object", ->
-      expect(Object.isFrozen(Point(3, 4).freeze()))
-      expect(Object.isFrozen(Point.zero().freeze()))
+    it "makes the Point object immutable", ->
+      expect(Object.isFrozen(Point(3, 4).freeze())).toBe true
+      expect(Object.isFrozen(Point.zero().freeze())).toBe true

   describe "::compare(other)", ->
     it "returns -1 for <, 0 for =, 1 for > comparisions", ->
-      expect(Point(2, 3).compare(Point(2, 6)) is -1)
-      expect(Point(2, 3).compare(Point(3, 4)) is -1)
-      expect(Point(1, 1).compare(Point(1, 1)) is 0)
-      expect(Point(2, 3).compare(Point(2, 0)) is 1)
-      expect(Point(2, 3).compare(Point(1, 3)) is 1)
-
-      expect(Point(2, 3).compare([2, 6]) is -1)
-      expect(Point(2, 3).compare([3, 4]) is -1)
-      expect(Point(1, 1).compare([1, 1]) is 0)
-      expect(Point(2, 3).compare([2, 0]) is 1)
-      expect(Point(2, 3).compare([1, 3]) is 1)
+      expect(Point(2, 3).compare(Point(2, 6))).toBe -1
+      expect(Point(2, 3).compare(Point(3, 4))).toBe -1
+      expect(Point(1, 1).compare(Point(1, 1))).toBe  0
+      expect(Point(2, 3).compare(Point(2, 0))).toBe  1
+      expect(Point(2, 3).compare(Point(1, 3))).toBe  1
+
+      expect(Point(2, 3).compare([2, 6])).toBe -1
+      expect(Point(2, 3).compare([3, 4])).toBe -1
+      expect(Point(1, 1).compare([1, 1])).toBe  0
+      expect(Point(2, 3).compare([2, 0])).toBe  1
+      expect(Point(2, 3).compare([1, 3])).toBe  1

   describe "::isLessThan(other)", ->
     it "returns a boolean indicating whether a point precedes the given Point ", ->
@@ -130,66 +130,74 @@

   describe "::min(a, b)", ->
     it "returns the minimum of two points", ->
-      expect(Point.min(Point(3, 4), Point(1, 1)).isEqual(Point(1, 1)))
-      expect(Point.min(Point(1, 2), Point(5, 6)).isEqual(Point(1, 2)))
-      expect(Point.min([3, 4], [1, 1]).isEqual([1, 1]))
-      expect(Point.min([1, 2], [5, 6]).isEqual([1, 2]))
+      expect(Point.min(Point(3, 4), Point(1, 1))).toEqual Point(1, 1)
+      expect(Point.min(Point(1, 2), Point(5, 6))).toEqual Point(1, 2)
+      expect(Point.min([3, 4], [1, 1])).toEqual [1, 1]
+      expect(Point.min([1, 2], [5, 6])).toEqual [1, 2]

   describe "::max(a, b)", ->
     it "returns the minimum of two points", ->
-      expect(Point.max(Point(3, 4), Point(1, 1)).isEqual(Point(3, 4)))
-      expect(Point.max(Point(1, 2), Point(5, 6)).isEqual(Point(5, 6)))
-      expect(Point.min([3, 4], [1, 1]).isEqual([3, 4]))
-      expect(Point.min([1, 2], [5, 6]).isEqual([5, 6]))
+      expect(Point.max(Point(3, 4), Point(1, 1))).toEqual Point(3, 4)
+      expect(Point.max(Point(1, 2), Point(5, 6))).toEqual Point(5, 6)
+      expect(Point.max([3, 4], [1, 1])).toEqual [3, 4]
+      expect(Point.max([1, 2], [5, 6])).toEqual [5, 6]

   describe "::sanitizeNegatives()", ->
     it "returns the point so that it has valid buffer coordinates", ->
-      expect(Point(-1, -1).sanitizeNegatives().isEqual(Point(0, 0)))
-      expect(Point(-1, 0).sanitizeNegatives().isEqual(Point(0, 0)))
-      expect(Point(-1, Infinity).sanitizeNegatives().isEqual(Point(0, 0)))
-
-      expect(Point(5, -1).sanitizeNegatives().isEqual(Point(5, 0)))
-      expect(Point(5, -Infinity).sanitizeNegatives().isEqual(Point(5, 0)))
-      expect(Point(5, 5).sanitizeNegatives().isEqual(Point(5, 5)))
+      expect(Point(-1, -1).sanitizeNegatives()).toEqual Point(0, 0)
+      expect(Point(-1, 0).sanitizeNegatives()).toEqual Point(0, 0)
+      expect(Point(-1, Infinity).sanitizeNegatives()).toEqual Point(0, 0)
+
+      expect(Point(5, -1).sanitizeNegatives()).toEqual Point(5, 0)
+      expect(Point(5, -Infinity).sanitizeNegatives()).toEqual Point(5, 0)
+      expect(Point(5, 5).sanitizeNegatives()).toEqual Point(5, 5)
+
+  describe "::translate(delta)", ->
+    it "returns a new point by adding corresponding coordinates", ->
+      expect(Point(1, 1).translate(Point(2, 3))).toEqual Point(3, 4)
+      expect(Point.infinity().translate(Point(2, 3))).toEqual Point.infinity()
+
+      expect(Point.zero().translate([5, 6])).toEqual [5, 6]
+      expect(Point(1, 1).translate([3, 4])).toEqual [4, 5]

   describe "::traverse(delta)", ->
     it "returns a new point by traversing given rows and columns", ->
-      expect(Point(2, 3).traverse(Point(0, 3)).isEqual(Point(2, 5)))
-      expect(Point(2, 3).traverse([0, 3]).isEqual([2, 5]))
-
-      expect(Point(1, 3).traverse(Point(4, 2)).isEqual([5, 2]))
-      expect(Point(1, 3).traverse([5, 4]).isEqual([6, 4]))
+      expect(Point(2, 3).traverse(Point(0, 3))).toEqual Point(2, 6)
+      expect(Point(2, 3).traverse([0, 3])).toEqual [2, 6]
+
+      expect(Point(1, 3).traverse(Point(4, 2))).toEqual [5, 2]
+      expect(Point(1, 3).traverse([5, 4])).toEqual [6, 4]

   describe "::traversalFrom(other)", ->
     it "returns a point that other has to traverse to get to given point", ->
-      expect(Point(2, 5).traversalFrom(Point(2, 3)).isEqual(Point(0, 2)))
-      expect(Point(2, 3).traversalFrom(Point(2, 5)).isEqual(Point(0, -2)))
-      expect(Point(2, 3).traversalFrom(Point(2, 3)).isEqual(Point(0, 0)))
-
-      expect(Point(3, 4).traversalFrom(Point(2, 3)).isEqual(Point(1, 4)))
-      expect(Point(2, 3).traversalFrom(Point(3, 5)).isEqual(Point(-1, 3)))
-
-      expect(Point(2, 5).traversalFrom([2, 3]).isEqual([0, 2]))
-      expect(Point(2, 3).traversalFrom([2, 5]).isEqual([0, -2]))
-      expect(Point(2, 3).traversalFrom([2, 3]).isEqual([0, 0]))
-
-      expect(Point(3, 4).traversalFrom([2, 3]).isEqual([1, 4]))
-      expect(Point(2, 3).traversalFrom([3, 5]).isEqual([-1, 3]))
+      expect(Point(2, 5).traversalFrom(Point(2, 3))).toEqual Point(0, 2)
+      expect(Point(2, 3).traversalFrom(Point(2, 5))).toEqual Point(0, -2)
+      expect(Point(2, 3).traversalFrom(Point(2, 3))).toEqual Point(0, 0)
+
+      expect(Point(3, 4).traversalFrom(Point(2, 3))).toEqual Point(1, 4)
+      expect(Point(2, 3).traversalFrom(Point(3, 5))).toEqual Point(-1, 3)
+
+      expect(Point(2, 5).traversalFrom([2, 3])).toEqual [0, 2]
+      expect(Point(2, 3).traversalFrom([2, 5])).toEqual [0, -2]
+      expect(Point(2, 3).traversalFrom([2, 3])).toEqual [0, 0]
+
+      expect(Point(3, 4).traversalFrom([2, 3])).toEqual [1, 4]
+      expect(Point(2, 3).traversalFrom([3, 5])).toEqual [-1, 3]

   describe "::toArray()", ->
     it "returns an array of row and column", ->
-      expect(Point(1, 3).toArray() is [1, 3])
-      expect(Point.zero().toArray() is [0, 0])
-      expect(Point.infinity().toArray() is [Infinity, Infinity])
+      expect(Point(1, 3).toArray()).toEqual [1, 3]
+      expect(Point.zero().toArray()).toEqual [0, 0]
+      expect(Point.infinity().toArray()).toEqual [Infinity, Infinity]

   describe "::serialize()", ->
     it "returns an array of row and column", ->
-      expect(Point(1, 3).serialize()  is [1, 3])
-      expect(Point.zero().serialize() is [0, 0])
-      expect(Point.infinity().serialize() is [Infinity, Infinity])
+      expect(Point(1, 3).serialize()).toEqual [1, 3]
+      expect(Point.zero().serialize()).toEqual [0, 0]
+      expect(Point.infinity().serialize()).toEqual [Infinity, Infinity]

   describe "::toString()", ->
     it "returns string representation of Point", ->
-      expect(Point(4, 5).toString()  is "(4, 5)")
-      expect(Point.zero().toString() is "(0, 0)")
-      expect(Point.infinity().toString() is "(Infinity, Infinity)")
+      expect(Point(4, 5).toString()).toBe "(4, 5)"
+      expect(Point.zero().toString()).toBe "(0, 0)"
+      expect(Point.infinity().toString()).toBe "(Infinity, Infinity)"


ENHANCE: Add analytics to changing project target grade

--- src/app/projects/partials/directives/project-contexts.coffee
+++ src/app/projects/partials/directives/project-contexts.coffee
@@ -3,7 +3,7 @@
 .directive('progressInfo', ->
   restrict: 'E'
   templateUrl: 'projects/partials/templates/progress-info.tpl.html'
-  controller: ($scope, $state, $rootScope, $stateParams, Project, Unit, UnitRole, headerService, alertService, gradeService, taskService, projectService) ->
+  controller: ($scope, $state, $rootScope, $stateParams, Project, Unit, UnitRole, headerService, alertService, gradeService, taskService, projectService, analyticsService) ->
     $scope.studentProjectId = $stateParams.projectId
     $scope.grades = gradeService.grades
     $scope.gradeAcronyms = gradeService.gradeAcronyms
@@ -17,6 +17,7 @@
         $scope.project.target_grade = project.target_grade
         $scope.project.burndown_chart_data = project.burndown_chart_data
         projectService.updateTaskStats $scope.project, project.stats
+        analyticsService.event "Student Feedback View - Progress Tab", "Grade Changed", idx
         $rootScope.$broadcast "TargetGradeUpdated"

     $scope.taskCount = () ->


another fixture story.

another fixture story.

--- server/fixtures.coffee
+++ server/fixtures.coffee
@@ -68,6 +68,27 @@
         content: "With a growing number of lives claimed, this disease has a tremendous impact on people’s lives. In many communities, the number of people infected by the disease far exceeds the capacity of hospitals to treat them. In some places, people do not believe that Ebola is the real cause of the deaths they are seeing, and are distrustful of hospitals. Some citizen journalists also report that families who experience ebola-related deaths face social stigma, which in turn causes them to avoid being tested at a hospital."
       }
     ]
+  Stories.insert
+    backgroundImage: "header-image.jpg"
+    headerImageAttribution: "HEALTHJASAREVIC / REUTERS"
+    lastSaved: new Date()
+    publishDate: new Date()
+    published: true
+    storyDashTitle: "test-story"
+    title: "Test building an existing sotry"
+    authorId: authorId
+    authorName: authorProfile.name # TODO update in user save methods
+    favorited: [] # TODO this should probably be usernames or something intelligible
+    verticalSections: [
+      {
+        _id: '931'
+        contextBlocks: []
+        title: "First block"
+        content: "Here is a great story."
+      }
+    ]
+
+

 if (ContextBlocks.find().count() is 0)


Pull just releases.

Pull just releases.

--- server/packages.coffee
+++ server/packages.coffee
@@ -2,4 +2,9 @@
 import { PackageServer } from "meteor/peerlibrary:meteor-packages"

 Meteor.startup ->
-  PackageServer.startSyncing()
+  PackageServer.startSyncing
+    logging: true
+    sync:
+      builds: false
+      releases: true
+      stats: false


Handle searches in new auto zoom model.

Handle searches in new auto zoom model.

--- src/map-state-model.coffee
+++ src/map-state-model.coffee
@@ -42,8 +42,8 @@
             mapBounds = @map.getBounds()
             # Don't pan just to center the view if the bounds are already
             # contained, unless the map can be zoomed in.
-            if bounds? and (@map.getZoom() == @map.getBoundsZoom(bounds) and
-                mapBounds.contains bounds) then return
+            if bounds? and (@map.getZoom() == @map.getBoundsZoom(bounds) and mapBounds.contains bounds)
+                return

             if @opts.route.has 'plan'
                 # Transit plan fitting is the simplest case, handle it and return.
@@ -66,21 +66,13 @@
                 viewOptions.center = MapUtils.latLngFromGeojson @opts.selectedPosition.value()
                 viewOptions.zoom = zoom

-            if @opts.services.size() and @opts.selectedUnits.isEmpty()
+            if @opts.services.size() or @opts.searchResults.size() and @opts.selectedUnits.isEmpty()
                 if bounds?
                     unless @opts.selectedPosition.isEmpty() and mapBounds.contains bounds
                         # Only zoom in, unless current map bounds is empty of units.
                         unitsInsideMap = @_objectsInsideBounds mapBounds, @opts.units
                         unless @opts.selectedPosition.isEmpty() and unitsInsideMap
                             viewOptions = @_widenViewMinimally @opts.units, viewOptions
-
-            else if @opts.searchResults.size()
-                if bounds?
-                    # Always zoom in to fit bounds, otherwise if there are no
-                    # visible results inside the viewport, fit bounds.
-                    if @_objectsInsideBounds mapBounds, @opts.units
-                        unless @map.getZoom() >= @map.getBoundsZoom(bounds)
-                            viewOptions.bounds = bounds

             @setMapView viewOptions

@@ -130,23 +122,27 @@

             topLatLngs = []
             unitsFound = {}
-            _.each @opts.services.pluck('id'), (id) =>
-                unitsFound[id] = UNIT_COUNT
+            if @opts.services.size()
+                _.each @opts.services.pluck('id'), (id) =>
+                    unitsFound[id] = UNIT_COUNT

-            # We want to have at least UNIT_COUNT visible units
-            # per service.
-            for unit in sortedUnits
-                if _.isEmpty unitsFound
-                    break
-                service = unit.collection.filters?.service
-                if service?
-                    countLeft = unitsFound[service]
-                    if countLeft?
-                        unitsFound[service] -= 1
-                        if unitsFound[service] == 0
-                            delete unitsFound[service]
-                    topLatLngs.push MapUtils.latLngFromGeojson(unit)
-
+                # We want to have at least UNIT_COUNT visible units
+                # per service.
+                for unit in sortedUnits
+                    if _.isEmpty unitsFound
+                        break
+                    service = unit.collection.filters?.service
+                    if service?
+                        countLeft = unitsFound[service]
+                        if countLeft?
+                            unitsFound[service] -= 1
+                            if unitsFound[service] == 0
+                                delete unitsFound[service]
+                        topLatLngs.push MapUtils.latLngFromGeojson(unit)
+            # All of the search results have to be visible.
+            else if @opts.searchResults.isSet()
+                topLatLngs = _(sortedUnits).map (unit) =>
+                    MapUtils.latLngFromGeojson(unit)
             if sortedUnits?.length
                 viewOptions.bounds =
                     L.latLngBounds topLatLngs


Get rid of listening to SIGINT.

Get rid of listening to SIGINT.

--- node_modules_koding/processes/src/processes.coffee
+++ node_modules_koding/processes/src/processes.coffee
@@ -23,11 +23,6 @@
 ################################
 ### HANDLE SIGNALS and KILLS ###
 ################################
-
-# when doing ctrl+c
-process.on 'SIGINT', =>
-  console.log "\nkilled via ctrl+c. this should not happen"
-  process.exit(0)

 # guaranteed death.
 process.on 'SIGTERM', =>


update label on remodaling

update label on remodaling

--- views/components/main/parts/miniship-parts/minipanebody.cjsx
+++ views/components/main/parts/miniship-parts/minipanebody.cjsx
@@ -123,16 +123,7 @@
     {label} = @state
     updateflag = false
     switch path
-      when '/kcsapi/api_port/port'
-        updateflag = true
-        label = @updateLabels()
-      when '/kcsapi/api_req_hensei/change'
-        updateflag = true
-        label = @updateLabels()
-      when '/kcsapi/api_req_hokyu/charge'
-        updateflag = true
-        label = @updateLabels()
-      when '/kcsapi/api_req_map/next'
+      when '/kcsapi/api_port/port', '/kcsapi/api_req_hensei/change', '/kcsapi/api_req_hokyu/charge', '/kcsapi/api_req_map/next', '/kcsapi/api_req_kaisou/remodeling', '/kcsapi/api_req_nyukyo/speedchange'
         updateflag = true
         label = @updateLabels()
       when '/kcsapi/api_req_nyukyo/start'
@@ -144,9 +135,6 @@
           if i isnt -1
             label[i] = 1
             updateflag = true
-      when '/kcsapi/api_req_nyukyo/speedchange'
-        updateflag = true
-        label = @updateLabels()
     if updateflag
       @setState
         label: label


fixed a few bugs

fixed a few bugs

--- src/plugins/history.coffee
+++ src/plugins/history.coffee
@@ -51,7 +51,7 @@
     fatalError "argument <idx> should be a whole number" unless _.isNumber(idx) and not _.isNaN(idx) and idx >= 0 and idx < history.length
     _path = history[idx]
     fatalError "path #{_path} is not a string" unless _.isString(_path)
-    schemeMsg "#{chalk.green 'changing'} working directory to #{chalk.grey _path}"
+    histMsg "#{chalk.green 'changing'} working directory to #{chalk.grey _path}"
     console.log _path
     programDone()

@@ -83,7 +83,7 @@
       fatalError "history is empty" if _.isEmpty(history)
       args = _commander.parent.rawArgs[3..]
       _path = util.fuzzySearch args, history
-      schemeMsg "#{chalk.green 'changing'} working directory to #{chalk.grey _path}"
+      histMsg "#{chalk.green 'changing'} working directory to #{chalk.grey _path}"
       console.log _path
       programDone()
     else
@@ -92,7 +92,7 @@
       fatalError "arugment <idx> should be less than #{history.length}" unless idx < history.length
       _path = history[idx]
       fatalError "path #{_path} is not a string" unless _.isString(_path)
-      schemeMsg "#{chalk.green 'changing'} working directory to #{chalk.grey _path}"
+      histMsg "#{chalk.green 'changing'} working directory to #{chalk.grey _path}"
       console.log _path
       programDone()


further compact less characters

further compact less characters

--- spec/narrow-spec.coffee
+++ spec/narrow-spec.coffee
@@ -1,5 +1,4 @@
 Ui = require '../lib/ui'
-{Range} = require 'atom'
 settings = require '../lib/settings'
 {
   startNarrow
@@ -12,6 +11,7 @@
   setActiveTextEditor
   setActiveTextEditorWithWaits
 } = require "./spec-helper"
+runCommand = dispatchEditorCommand

 appleGrapeLemmonText = """
   apple
@@ -55,27 +55,24 @@
     describe "closeOnConfirm settings", ->
       it "land to confirmed item and close narrow-editor", ->
         settings.set('Scan.closeOnConfirm', true)
-        narrow.waitsForDestroy -> dispatchEditorCommand('core:confirm')
+        narrow.waitsForDestroy -> runCommand('core:confirm')
         runs ->
           ensureEditor editor, cursor: [0, 3]
-          ensureEditor ui.editor, alive: false
           expect(Ui.getSize()).toBe(0)

       it "land to confirmed item and keep open narrow-editor", ->
         settings.set('Scan.closeOnConfirm', false)
-        narrow.waitsForConfirm -> dispatchEditorCommand('core:confirm')
+        narrow.waitsForConfirm -> runCommand('core:confirm')
         runs ->
           ensureEditor editor, cursor: [0, 3]
-          ensureEditor ui.editor, alive: true
           expect(Ui.getSize()).toBe(1)

     describe "confirm-keep-open command", ->
       it "land to confirmed item and keep open narrow-editor even if closeOnConfirm was true", ->
         settings.set('Scan.closeOnConfirm', true)
-        narrow.waitsForConfirm -> dispatchEditorCommand('narrow-ui:confirm-keep-open')
+        narrow.waitsForConfirm -> runCommand('narrow-ui:confirm-keep-open')
         runs ->
           ensureEditor editor, cursor: [0, 3]
-          ensureEditor ui.editor, alive: true
           expect(Ui.getSize()).toBe(1)

   describe "narrow-editor open/close", ->
@@ -150,10 +147,9 @@

     it "toggle focus between provider.editor and ui.editor", ->
       ensureEditorIsActive(ui.editor)
-      dispatchEditorCommand('narrow:focus')
-      ensureEditorIsActive(editor)
-      dispatchEditorCommand('narrow:focus')
-      ensureEditorIsActive(ui.editor)
+      runCommand('narrow:focus'); ensureEditorIsActive(editor)
+      runCommand('narrow:focus'); ensureEditorIsActive(ui.editor)
+      runCommand('narrow:focus'); ensureEditorIsActive(editor)

   describe "narrow:focus-prompt", ->
     beforeEach ->
@@ -167,16 +163,15 @@
       ensureEditorIsActive(ui.editor)
       ensure cursor: [1, 0], selectedItemRow: 1

-      dispatchEditorCommand('narrow:focus-prompt') # focus from item-area to prompt
-      ensureEditorIsActive(ui.editor)
+      runCommand('narrow:focus-prompt'); ensureEditorIsActive(ui.editor)
       ensure cursor: [0, 0], selectedItemRow: 1

-      dispatchEditorCommand('narrow:focus-prompt') # focus provider.editor
-      ensureEditorIsActive(editor)
+      # focus provider.editor
+      runCommand('narrow:focus-prompt'); ensureEditorIsActive(editor)
       ensure cursor: [0, 0], selectedItemRow: 1

-      dispatchEditorCommand('narrow:focus-prompt') # focus narrow-editor
-      ensureEditorIsActive(ui.editor)
+      # focus narrow-editor
+      runCommand('narrow:focus-prompt'); ensureEditorIsActive(ui.editor)
       ensure cursor: [0, 0], selectedItemRow: 1

   describe "narrow:close", ->
@@ -187,9 +182,9 @@

     it "close narrow-editor from outside of narrow-editor", ->
       expect(atom.workspace.getTextEditors()).toHaveLength(2)
-      paneForItem(editor).activate()
+      setActiveTextEditor(editor)
       ensureEditorIsActive(editor)
-      dispatchEditorCommand('narrow:close')
+      runCommand('narrow:close')
       ensureEditorIsActive(editor)
       expect(atom.workspace.getTextEditors()).toHaveLength(1)

@@ -200,18 +195,12 @@

       runs ->
         expect(Ui.getSize()).toBe(4)
-        paneForItem(editor).activate()
-        ensureEditorIsActive(editor)
-        dispatchEditorCommand('narrow:close')
-        expect(Ui.getSize()).toBe(3)
-        dispatchEditorCommand('narrow:close')
-        expect(Ui.getSize()).toBe(2)
-        dispatchEditorCommand('narrow:close')
-        expect(Ui.getSize()).toBe(1)
-        dispatchEditorCommand('narrow:close')
-        expect(Ui.getSize()).toBe(0)
-        dispatchEditorCommand('narrow:close')
-        expect(Ui.getSize()).toBe(0)
+        setActiveTextEditor(editor); ensureEditorIsActive(editor)
+        runCommand('narrow:close'); expect(Ui.getSize()).toBe(3)
+        runCommand('narrow:close'); expect(Ui.getSize()).toBe(2)
+        runCommand('narrow:close'); expect(Ui.getSize()).toBe(1)
+        runCommand('narrow:close'); expect(Ui.getSize()).toBe(0)
+        runCommand('narrow:close'); expect(Ui.getSize()).toBe(0)

   describe "narrow:refresh", ->
     beforeEach ->
@@ -228,14 +217,14 @@
         ensure text: "\nabc\ndef\n"

       narrow.waitsForRefresh ->
-        dispatchEditorCommand('narrow:refresh')
+        runCommand('narrow:refresh')

       runs ->
         ensure text: originalText

   describe "narrow:next-item, narrow:previous-item", ->
-    nextItem = -> runs -> narrow.waitsForConfirm -> dispatchEditorCommand('narrow:next-item')
-    previousItem = -> runs -> narrow.waitsForConfirm -> dispatchEditorCommand('narrow:previous-item')
+    nextItem = -> runs -> narrow.waitsForConfirm -> runCommand('narrow:next-item')
+    previousItem = -> runs -> narrow.waitsForConfirm -> runCommand('narrow:previous-item')

     describe "basic behavior", ->
       beforeEach ->
@@ -253,18 +242,19 @@
               """

       it "move to next/previous item with wrap", ->
-        paneForItem(editor).activate()
-        ensureEditorIsActive(editor)
+        setActiveTextEditor(editor); ensureEditorIsActive(editor)
         ensureEditor editor, cursor: [0, 0]

-        nextItem(); runs -> ensureEditor editor, cursor: [0, 1]
-        nextItem(); runs -> ensureEditor editor, cursor: [0, 2]
-        nextItem(); runs -> ensureEditor editor, cursor: [1, 3]
-        nextItem(); runs -> ensureEditor editor, cursor: [0, 1]
-
-        previousItem(); runs -> ensureEditor editor, cursor: [1, 3]
-        previousItem(); runs -> ensureEditor editor, cursor: [0, 2]
-        previousItem(); runs -> ensureEditor editor, cursor: [0, 1]
+        _ensureEditor = (options) -> runs -> ensureEditor(editor, options)
+
+        nextItem(); _ensureEditor cursor: [0, 1]
+        nextItem(); _ensureEditor cursor: [0, 2]
+        nextItem(); _ensureEditor cursor: [1, 3]
+        nextItem(); _ensureEditor cursor: [0, 1]
+
+        previousItem(); _ensureEditor cursor: [1, 3]
+        previousItem(); _ensureEditor cursor: [0, 2]
+        previousItem(); _ensureEditor cursor: [0, 1]

     describe "when cursor position is contained in item.range", ->
       beforeEach ->
@@ -289,36 +279,37 @@
               """

       it "move to next/previous", ->
+        jasmine.useRealClock()
+
+        setActiveTextEditorWithWaits(editor)
+
+        r1 = [[0, 0], [0, 3]] # `line` range of "line 1"
+        r2 = [[1, 2], [1, 6]] # `line` range of "  line 2"
+        r3 = [[2, 0], [2, 3]] # `line` range of "line 3"
+        r4 = [[3, 2], [3, 6]] # `line` range of "  line 4"
+
         setCursor = (point) -> runs -> editor.setCursorBufferPosition(point)
-
-        jasmine.useRealClock()
-
-        setActiveTextEditorWithWaits(editor)
-
-        r1 = new Range([0, 0], [0, 3])
-        r2 = new Range([1, 2], [1, 6])
-        r3 = new Range([2, 0], [2, 3])
-        r4 = new Range([3, 2], [3, 6])
-
-        setCursor(r1.start); nextItem(); runs -> ensureEditor editor, cursor: [1, 2]
-        setCursor(r1.end); nextItem(); runs -> ensureEditor editor, cursor: [1, 2]
-        setCursor(r1.start); previousItem(); runs -> ensureEditor editor, cursor: [3, 2]
-        setCursor(r1.end); previousItem(); runs -> ensureEditor editor, cursor: [3, 2]
-
-        setCursor(r2.start); nextItem(); runs -> ensureEditor editor, cursor: [2, 0]
-        setCursor(r2.end); nextItem(); runs -> ensureEditor editor, cursor: [2, 0]
-        setCursor(r2.start); previousItem(); runs -> ensureEditor editor, cursor: [0, 0]
-        setCursor(r2.end); previousItem(); runs -> ensureEditor editor, cursor: [0, 0]
-
-        setCursor(r3.start); nextItem(); runs -> ensureEditor editor, cursor: [3, 2]
-        setCursor(r3.end); nextItem(); runs -> ensureEditor editor, cursor: [3, 2]
-        setCursor(r3.start); previousItem(); runs -> ensureEditor editor, cursor: [1, 2]
-        setCursor(r3.end); previousItem(); runs -> ensureEditor editor, cursor: [1, 2]
-
-        setCursor(r4.start); nextItem(); runs -> ensureEditor editor, cursor: [0, 0]
-        setCursor(r4.end); nextItem(); runs -> ensureEditor editor, cursor: [0, 0]
-        setCursor(r4.start); previousItem(); runs -> ensureEditor editor, cursor: [2, 0]
-        setCursor(r4.end); previousItem(); runs -> ensureEditor editor, cursor: [2, 0]
+        ensureCursor = (point) -> runs -> ensureEditor(editor, cursor: point)
+
+        setCursor(r1[0]); nextItem(); ensureCursor([1, 2])
+        setCursor(r1[1]); nextItem(); ensureCursor([1, 2])
+        setCursor(r1[0]); previousItem(); ensureCursor([3, 2])
+        setCursor(r1[1]); previousItem(); ensureCursor([3, 2])
+
+        setCursor(r2[0]); nextItem(); ensureCursor([2, 0])
+        setCursor(r2[1]); nextItem(); ensureCursor([2, 0])
+        setCursor(r2[0]); previousItem(); ensureCursor([0, 0])
+        setCursor(r2[1]); previousItem(); ensureCursor([0, 0])
+
+        setCursor(r3[0]); nextItem(); ensureCursor([3, 2])
+        setCursor(r3[1]); nextItem(); ensureCursor([3, 2])
+        setCursor(r3[0]); previousItem(); ensureCursor([1, 2])
+        setCursor(r3[1]); previousItem(); ensureCursor([1, 2])
+
+        setCursor(r4[0]); nextItem(); ensureCursor([0, 0])
+        setCursor(r4[1]); nextItem(); ensureCursor([0, 0])
+        setCursor(r4[0]); previousItem(); ensureCursor([2, 0])
+        setCursor(r4[1]); previousItem(); ensureCursor([2, 0])

   describe "narrow-editor auto-sync selected-item to active editor", ->
     [editor2] = []
@@ -413,12 +404,15 @@
         runs ->
           ensureEditorIsActive(editor)
           expect(provider.editor).toBe(editor)
-          editor.setCursorBufferPosition([0, 0])
+
+          setCursor = (point) -> editor.setCursorBufferPosition(point)
+
+          setCursor([0, 0])
           ensure selectedItemText: "line 1"
-          editor.setCursorBufferPosition([1, 0]); ensure selectedItemText: "line 1"
-          editor.setCursorBufferPosition([2, 0]); ensure selectedItemText: "line 3"
-          editor.setCursorBufferPosition([3, 0]); ensure selectedItemText: "line 3"
-          editor.setCursorBufferPosition([4, 0]); ensure selectedItemText: "line 5"
+          setCursor([1, 0]); ensure selectedItemText: "line 1"
+          setCursor([2, 0]); ensure selectedItemText: "line 3"
+          setCursor([3, 0]); ensure selectedItemText: "line 3"
+          setCursor([4, 0]); ensure selectedItemText: "line 5"
           editor.moveToBottom()
           ensureEditor editor, cursor: [9, 0]
           ensure selectedItemText: "line 9"
@@ -426,12 +420,14 @@
         setActiveTextEditorWithWaits(editor2)

         runs ->
+          setCursor = (point) -> editor2.setCursorBufferPosition(point)
+
           expect(provider.editor).toBe(editor2)
           ensure selectedItemText: "line 2"
-          editor2.setCursorBufferPosition([1, 0]); ensure selectedItemText: "line 2"
-          editor2.setCursorBufferPosition([3, 0]); ensure selectedItemText: "line 4"
-          editor2.setCursorBufferPosition([5, 0]); ensure selectedItemText: "line 6"
-          editor2.setCursorBufferPosition([7, 0]); ensure selectedItemText: "line 8"
+          setCursor([1, 0]); ensure selectedItemText: "line 2"
+          setCursor([3, 0]); ensure selectedItemText: "line 4"
+          setCursor([5, 0]); ensure selectedItemText: "line 6"
+          setCursor([7, 0]); ensure selectedItemText: "line 8"

           editor2.moveToTop()
           ensureEditor editor2, cursor: [0, 0]
@@ -439,7 +435,7 @@

   describe "scan", ->
     describe "with empty qury", ->
-      confirm = -> narrow.waitsForDestroy -> dispatchEditorCommand('core:confirm')
+      confirm = -> narrow.waitsForDestroy -> runCommand('core:confirm')
       beforeEach ->
         editor.setText(appleGrapeLemmonText)
         editor.setCursorBufferPosition([0, 0])


No longer require atom in the spec-helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -9,11 +9,14 @@
 fs = require 'fs-plus'
 Grim = require 'grim'
 KeymapManager = require '../src/keymap-extensions'
-Workspace = require '../src/workspace'
+
+# FIXME: Remove jquery from this
 {$} = require '../src/space-pen-extensions'
+
 Config = require '../src/config'
 {Point} = require 'text-buffer'
 Project = require '../src/project'
+Workspace = require '../src/workspace'
 TextEditor = require '../src/text-editor'
 TextEditorView = require '../src/text-editor-view'
 TokenizedBuffer = require '../src/tokenized-buffer'


catch outcome statement if changed

catch outcome statement if changed

--- vue/src/shared/services/rescue_unsaved_edits_service.coffee
+++ vue/src/shared/services/rescue_unsaved_edits_service.coffee
@@ -6,7 +6,7 @@
     @models = []

   okToLeave: (model) ->
-    attrs = ['description', 'title', 'body', 'details', 'name', 'reason']
+    attrs = ['description', 'title', 'body', 'details', 'name', 'reason', 'statement']
     models = ((model && [model]) || @models)
     if some(models, (m) -> intersection(attrs, m.modifiedAttributes()).length > 0)
       # console.log 'some modified', @models.map (m) ->


Improve autoLabel()

Improve autoLabel()

--- src/SchemaUtils.coffee
+++ src/SchemaUtils.coffee
@@ -101,8 +101,9 @@
     if label?
       label
     else
+      id = id.replace(/^.*\./, '')
       label = id.replace('_', '')
-      Strings.toTitleCase(label)
+      Strings.toTitleCaseFromCamel(label)

   getFieldLabel: (paramId) ->
     field = @getParameterField(paramId)


Remove _getOnly meta property.

Remove _getOnly meta property.

--- base-site/_default.meta.coffee
+++ base-site/_default.meta.coffee
@@ -39,13 +39,6 @@
   # level of middleware, but instead result in rerouting to a custom _500 page.
   # A default _500 page will be shown if one has not been defined for the site.
   _custom_500: false
-
-  # If getOnly is true, only GET or HEAD requests will be handled, unless a
-  # request-type specific handler is found.
-  # For example, if a POST request is received for /foo/bar, the request will
-  # be 404ed unless there is a *.post.js, *.post.coffee or other such post-
-  # specific handler
-  _getOnly: true

   # Valid URLs may include the following extensions.
   # Any other extensions at the end of a URL will be disallowed.


Workspace: Fixed additional button options.

--- client/landing/app/CommonViews/workspace/panel/panel.coffee
+++ client/landing/app/CommonViews/workspace/panel/panel.coffee
@@ -27,6 +27,7 @@
       partial   : """#{title}"""

   createHeaderButtons: ->
+    # TODO: fatihacet - DRY
     @getOptions().buttons.forEach (buttonOptions) =>
       if buttonOptions.itemClass
         Klass = buttonOptions.itemClass
@@ -35,11 +36,8 @@

         buttonView = new Klass buttonOptions
       else
-        buttonView = new KDButtonView
-          title    : buttonOptions.title
-          cssClass : buttonOptions.cssClass
-          callback : =>
-            buttonOptions.callback @, @getDelegate()
+        buttonOptions.callback = buttonOptions.callback?.bind this, this, @getDelegate()
+        buttonView = new KDButtonView buttonOptions

       @headerButtons[buttonOptions.title] = buttonView
       @header.addSubView buttonView


Small fixes for UI

Small fixes for UI

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -50,9 +50,9 @@
                 label         : "Flags"
                 placeholder   : "no flags assigned"
               Invites         :
-                label         : "Invites"
+                label         : "Add Invites"
                 type          : "text"
-                placeholder   : "number of invites to give that user"
+                placeholder   : "number of invites to add"
                 validate      :
                   rules       :
                     regExp    : /\d+/i
@@ -186,8 +186,8 @@
         account.fetchLimit? 'invite', (err, limit)=>
           current = 0
           if not err and limit
-             current = limit.quota - limit.usage
-          inputs.Invites.setValue current
+            current = limit.quota - limit.usage
+          inputs.Invites.setPlaceHolder "Currently has #{current} invites."
       else
         userRequestLineEdit.show()
         @hideConnectedFields()
@@ -213,5 +213,5 @@
     fields.Impersonate.show()
     fields.Flags.show()
     fields.Invites.show()
-    inputs.Invites.setValue 'Loading...'
+    inputs.Invites.setPlaceHolder 'Loading...'
     buttons.Update.show()


Added comments for icon methods.

Added comments for icon methods.

--- app/assets/javascripts/app/models/_application_model.coffee
+++ app/assets/javascripts/app/models/_application_model.coffee
@@ -69,12 +69,15 @@
       return @login
     return '???'

+  # shows the icon representing the object itself (e. g. the organization icon in organization profile or ticket sidebar)
   icon: (user) ->
     ''

+  # shows icons in a list of objects (e. g. the traffic light rings in the ticket list in user profile)
   iconTitle: (user) ->
     ''

+  # shows icons in the activity stream (e. g. show ! if the activity stream icon is belonging to the session user)
   iconActivity: (user) ->
     ''


Clean up embedly adapter

Clean up embedly adapter

--- app/assets/javascripts/embeditor/adapters/embedly.js.coffee
+++ app/assets/javascripts/embeditor/adapters/embedly.js.coffee
@@ -1,4 +1,6 @@
-class Embeditor.Adapters.Embedly extends Embeditor.Adapters.Oembed
+# This doesn't inherit from Adapters.Oembed because the Embedly plugin
+# handles most of that stuff on its own.
+class Embeditor.Adapters.Embedly extends Embeditor.Adapter
     # This object should hold any keys that we want to
     # send to the API. Any key not in this object will
     # be ignored as a data attribute.
@@ -11,12 +13,10 @@
         endpoint    : 'oembed'
         # Key must be specified

+
     constructor: (@element, options={}) ->
-        @dataOptions = @_extractData()
-        @queryParams = @_buildParams(@dataOptions, options)
-
-        @pluginOptions = _.defaults(options['Embedly']?['plugin'],
-            Embedly.PluginDefaults)
+        pluginOpts = options['Embedly']?['plugin'] or {}
+        @pluginOptions = _.defaults(pluginOpts, Embedly.PluginDefaults)

         super


Changes api link to https

Changes api link to https

--- app/assets/javascripts/embeditor/adapters/embedly.js.coffee
+++ app/assets/javascripts/embeditor/adapters/embedly.js.coffee
@@ -1,7 +1,7 @@
 class Embeditor.Adapters.Embedly extends Embeditor.Adapters.Oembed
     className: "Embedly"

-    @Endpoint = "http://api.embed.ly/1/oembed"
+    @Endpoint = "https://api.embed.ly/1/oembed"

     # This object should hold any keys that we want to
     # send to the API. Any key not in this object will


Prepend messages

Prepend messages

--- app/assets/javascripts/georgia/views/panels/panel.js.coffee
+++ app/assets/javascripts/georgia/views/panels/panel.js.coffee
@@ -22,7 +22,7 @@

   notify: (message, status = 'warning') ->
     view = new Georgia.Views.Message(message: message, status: status)
-    @$('.messages').append(view.render().el)
+    @$('.messages').prepend(view.render().el)

   activateTabs: ->
     @$('.form .nav.nav-tabs > li:first-child > a').trigger('click')


removed draggable;

removed draggable;

--- app/assets/javascripts/lolita/file-upload/gallery.js.coffee
+++ app/assets/javascripts/lolita/file-upload/gallery.js.coffee
@@ -48,18 +48,6 @@

   _image_and_icon: ->
     $(IMAGE_SELECTOR + ',' + FILE_ICON_SELECTOR)
-
-  _makeImagesDraggable: ->
-    @_image_and_icon().each(->
-      if $(this).is('.ui-draggable')
-        $(this).draggable("destroy")
-    )
-    @_image_and_icon().draggable({
-      appendTo: "body"
-      zIndex: 20000
-      helper: "clone"
-      cursor: "move"
-    })

   _addImagesToDOM: ->
     while image = @._imageQueue.shift()
@@ -127,7 +115,6 @@
   _addToDOM: ($imageContainer) ->
     @_tape().append($imageContainer)
     @_slider().elastislide("add", $imageContainer)
-    @_makeImagesDraggable()

   _addToQueue: ($imageContainer) ->
     @._imageQueue.push($imageContainer)


#1053 Only rerequest dirty buckets on pull failure

--- app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
@@ -103,7 +103,8 @@
         )
         =>
           for bucket in batch
-            @add({bucket : bucket, priority : @PRIORITY_HIGHEST})
+            if @cube.getBucketByZoomedAddress(item.bucket).dirty
+              @add({bucket : bucket, priority : @PRIORITY_HIGHEST})
       )
       =>
         @batchCount--


change 'iid' to 'intervalId'

change 'iid' to 'intervalId'

--- app/assets/javascripts/services/heartbeat_service.js.coffee
+++ app/assets/javascripts/services/heartbeat_service.js.coffee
@@ -15,10 +15,10 @@
     @set('intervalId', setInterval(heartbeatWrapper, @get('interval')))

   stop: ->
-    iid = @get('intervalId')
-    if iid
-      clearInterval(iid)
-      @set('iid', null)
+    intervalId = @get('intervalId')
+    if intervalId
+      clearInterval(intervalId)
+      @set('intervalId', null)

   heartbeat: ->
     $.ajax


properly set socket.io configuration from options

--- src/server/socketio.coffee
+++ src/server/socketio.coffee
@@ -20,7 +20,9 @@

 	io.configure ->
 		io.set 'log level', 1
-
+		for option in options
+			io.set option, options[option]
+
 	authClient = (handshakeData, callback) ->
 		data =
 			headers: handshakeData.headers


metric: fix reverse data

metric: fix reverse data

--- src/services/metric.coffee
+++ src/services/metric.coffee
@@ -34,9 +34,9 @@
     "#{result} AND #{where}"

 dayRangeQuery = (model, {query, fromDay, toDay}) ->
-  util.forkJoin _.map(_.range(toDay - fromDay), (day) ->
+  util.forkJoin _.map(_.range(fromDay, toDay), (day) ->
     model.event.query queryify _.defaults {
-      where: query.where(toDay - day)
+      where: query.where(day)
     }, query
   )
   .map (partials) ->


RootView.initialize no longer assigns window.rootView or calls open

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -10,7 +10,8 @@
 describe "Snippets extension", ->
   [buffer, editor, editSession] = []
   beforeEach ->
-    new RootView(project.resolve('sample.js'))
+    window.rootView = new RootView
+    rootView.open('sample.js')
     spyOn(LoadSnippetsTask.prototype, 'start')

     packageWithSnippets = window.loadPackage("package-with-snippets")


use keydown instead of triggering keydownEvent

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -90,7 +90,7 @@
         keydown('d', element: editor[0])
         expect(vimState.opStack.length).toBe 1

-        editor.trigger keydownEvent('ctrl-c')
+        keydown('c', ctrl: true, element: editor[0])
         expect(vimState.opStack.length).toBe 0

     describe "the i keybinding", ->
@@ -864,7 +864,7 @@
     it "puts the editor into command mode when <ctrl-c> is pressed", ->
       expect(editor).not.toHaveClass 'command-mode'

-      editor.trigger keydownEvent('ctrl-c')
+      keydown('c', ctrl: true, element: editor[0])

       expect(editor).toHaveClass 'command-mode'
       expect(editor).not.toHaveClass 'insert-mode'


PermissionSet: check current group from client.context if possible

--- workers/social/lib/social/models/group/permissionset.coffee
+++ workers/social/lib/social/models/group/permissionset.coffee
@@ -124,6 +124,7 @@

     advanced     = wrapPermission advanced  if 'string' is typeof advanced
     anyValidator = (require './validators').any
+    currentGroup = client?.context?.group ? MAIN_GROUP

     # permission checker helper, walks on the all required permissions
     # if one of them passes, breaks the loop and returns true
@@ -135,7 +136,7 @@

           # if permission requires superadmin and current group is not 'koding'
           # or if somehow 'koding' group (main) not exists then pass ~ GG
-          if current.group.slug isnt MAIN_GROUP or not main
+          if currentGroup isnt MAIN_GROUP or not main
             return queue.next()

           # if permission requires superadmin then do the permission check on


hide show live updates switcher for guests

--- client/landing/app/Applications/Activity.kdapplication/views/activitylistheader.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/activitylistheader.coffee
@@ -24,6 +24,9 @@
         @updateShowNewItemsLink()
         @getSingleton('activityController').flags = liveUpdates : state
         @getSingleton('activityController').emit "LiveStatusUpdateStateChanged", state
+
+    @getSingleton('mainController').on 'AccountChanged', @bound 'decorateLiveUpdateButton'
+    @decorateLiveUpdateButton()

     if KD.checkFlag "super-admin"
       @lowQualitySwitch = new KDOnOffSwitch
@@ -58,6 +61,10 @@
     @_newItemsCount++
     @updateShowNewItemsLink()

+  decorateLiveUpdateButton:->
+    if KD.isLoggedIn() then @liveUpdateButton.show()
+    else @liveUpdateButton.hide()
+
   updateShowNewItemsLink:(showNewItems = no)->
     if @_newItemsCount > 0
       if @liveUpdateButton.getValue() is yes or showNewItems is yes


Modify slider default properties

Modify slider default properties

--- assets/javascripts/react/components/bootstrap_slider.coffee
+++ assets/javascripts/react/components/bootstrap_slider.coffee
@@ -5,9 +5,9 @@
   getDefaultProps: ->
     {
       min: 0
-      max: 100
+      max: 1000
       step: 1
-      value: 50
+      value: 500
       toolTip: true
       onChange: ->
     }


updated link to co2 sheet on etm startpage


DE24548: Timeboxes page - Extend bulk edit for iterations and releases apps

--- test/spec/timeboxes/TimeboxesAppSpec.coffee
+++ test/spec/timeboxes/TimeboxesAppSpec.coffee
@@ -68,8 +68,8 @@
     it 'should have an xml export button', ->
       expect(@app.xmlExportEnabled()).toBe true

-    it 'should not allow editing of the Name field', ->
-      expect(_.find(@app.gridboard.getGridOrBoard().columns, (column) -> column.dataIndex =='Name').tdCls).not.toContain 'editable'
+    it 'should allow editing of the Name field', ->
+      expect(_.find(@app.gridboard.getGridOrBoard().columns, (column) -> column.dataIndex =='Name').tdCls).toContain 'editable'

   describe 'releases', ->
     beforeEach ->
@@ -87,8 +87,8 @@
     it 'should have an xml export button', ->
       expect(@app.xmlExportEnabled()).toBe true

-    it 'should not allow editing of the Name field', ->
-      expect(_.find(@app.gridboard.getGridOrBoard().columns, (column) -> column.dataIndex =='Name').tdCls).not.toContain 'editable'
+    it 'should allow editing of the Name field', ->
+      expect(_.find(@app.gridboard.getGridOrBoard().columns, (column) -> column.dataIndex =='Name').tdCls).toContain 'editable'

   describe 'on type change', ->
     beforeEach ->


Add better error message for network error.

At least, something. Not tested...

--- kirppu/static_src/js/checkout/countermode.coffee
+++ kirppu/static_src/js/checkout/countermode.coffee
@@ -58,6 +58,7 @@

   showError: (status, text, code) =>
     switch status
+      when 0 then errorMsg = "Network disconnected!"
       when 404 then errorMsg = "Item is not registered."
       when 409 then errorMsg = text
       when 423 then errorMsg = text


Separated adding new introduction group to 2 form.

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -411,7 +411,7 @@

     @buttonsContainer.addSubView @addButton = new KDButtonView
       cssClass : "editor-button"
-      title    : "Add New"
+      title    : "Add New Introduction Group"
       callback : => @showForm()

     @container = new KDView
@@ -462,12 +462,13 @@
           delegate: @
         ,snippet

-  showForm: (type = "Group", data = @getData(), actionType = "Insert") ->
+  showForm: (type = "Group", data = @getData(), actionType = "Insert", addingToAGroup = no) ->
     @buttonsContainer.hide()
     @container.hide()
     @addSubView @form = new IntroductionAdminForm {
       type
       actionType
+      addingToAGroup
       delegate: @
     }
     , data
@@ -637,7 +638,6 @@
             itemClass : KDView
             partial   : "Date format should be YYYY-MM-DD. e.g. 2013-05-03"
             cssClass  : "help-text info"
-
       Overlay         :
         label         : "Overlay"
         type          : "select"
@@ -653,7 +653,6 @@
             cssClass  : "help-text warning"
         change: =>
           @setVisibilityOfOverlayWarning @inputs.Overlay.getValue() is "no" # wtf "no"
-
       Visibility       :
         label         : "Visibility"
         type          : "select"
@@ -686,11 +685,7 @@
             cssClass  : "introduction-snippet-switch snippet"
             click     : => @switchMode()

-    options.fields  = itemFields
-    if options.type is "Group"
-      if options.actionType is "Insert"
-        groupFields[key] = field for key, field of itemFields
-      options.fields = groupFields
+    options.fields = if options.type is "Group" then groupFields else itemFields

     options.buttons =
       "Save"          :
@@ -713,15 +708,15 @@

     super options, data

-    {type, actionType} = @getOptions()
-    @utils.defer => @setAceEditor() if (type is "Group" and actionType is "Insert") or type is "Item"
-
-    @setVisibilityOfOverlayWarning @parentData.overlay is "no" # wtf "no"
+    @utils.defer => @setAceEditor() if @getOptions().type is "Item"
+    if @getOptions().type is "Group"
+      @setVisibilityOfOverlayWarning @parentData.overlay is "no" # wtf "no"

   save: ->
-    {actionType} = @getOptions()
-
-    if @getOptions().type is "Group"
+    options      = @getOptions()
+    {actionType, addingToAGroup} = options
+
+    if @getOptions().type is "Group" or addingToAGroup
       return @insertParent() if actionType is "Insert"
       @updateParent()
     else
@@ -729,9 +724,15 @@
       @updateChild()

   insertParent: ->
-    KD.remote.api.JIntroSnippet.create @createPostData(), =>
-      @buttons["Save"].hideLoader()
-      @emit "IntroductionFormNeedsReload"
+    data = @createPostData()
+    if not @getOptions().addingToAGroup
+      @destroy()
+      @getDelegate().showForm "Item", data, "Insert", yes
+    else
+      @parentData.snippets.push data
+      KD.remote.api.JIntroSnippet.create @parentData, =>
+        @buttons["Save"].hideLoader()
+        @emit "IntroductionFormNeedsReload"

   insertChild: ->
     @parentData.addChild @createPostData(), =>
@@ -750,33 +751,26 @@

   createPostData: ->
     {inputs}     = @
-    options      = @getOptions()
-    {type}       = options
-    {actionType} = options
-    itemData     = {}
-
-    snippet = @aceEditor?.getValue()
-    if @snippetType is "Text"
-      snippet = """new KDView({partial: "#{@aceEditor?.getValue()}"});"""
-
-    if type is "Item" or (type is "Group" and actionType is "Insert")
-      itemData = {
-        introId    : inputs.IntroId.getValue()
-        introTitle : inputs.IntroTitle.getValue()
-        snippet
-      }
-
-    if type is "Group"
-      data = {
+
+    if @getOptions().type is "Group"
+      groupData =
         title      : inputs.Title.getValue()
         expiryDate : inputs.ExpiryDate.getValue()
         visibility : inputs.Visibility.getValue()
         overlay    : inputs.Overlay.getValue()
-      }
-      data.snippets = [ itemData ] if actionType is "Insert"
-      return data
-
-    return itemData
+        snippets   : []
+      return groupData
+
+    else
+      editorValue = @aceEditor?.getValue()
+      snippet     = """new KDView({partial: "#{editorValue}"});""" if @snippetType is "Text"
+
+      itemData    =
+        introId    : inputs.IntroId.getValue()
+        introTitle : inputs.IntroTitle.getValue()
+        snippet    : snippet or editorValue
+
+      return itemData

   setVisibilityOfOverlayWarning: (isShowing) ->
     @warningView = @warningView or @fields?.warning.getSubViews()[1].getSubViews()[0]


removed name from example

removed name from example

--- examples/app.coffee
+++ examples/app.coffee
@@ -8,7 +8,7 @@

 # nconf reads in config values from json file
 nconf.argv().env()
-nconf.file({ file: '/Users/georgi/dev/projects/etsyJS/examples/config.json' })
+nconf.file({ file: './examples/config.json' })

 # instantiate client with key and secret and set callback url
 client = etsyjs.client
@@ -32,7 +32,7 @@
       res.redirect response.loginUrl
   else
     # else if we have OAuth credentials for this session then use them
-    client.auth(oauthSession.token, oauthSession.secret).user("georgiknox").find (err, body, headers) ->
+    client.auth(oauthSession.token, oauthSession.secret).user("sparkleprincesspony").find (err, body, headers) ->
       console.log err if err
       console.dir "Returned result #{body}" if body
       res.send body.results[0] if body
@@ -48,7 +48,7 @@
   oauthSession = {token: req.session.token, secret: req.session.sec}
   console.log("updating profile...")
   updatedProfile = {user_id: "georgiknox", city: "New York City"}
-  client.auth(oauthSession.token, oauthSession.secret).user("georgiknox").updateUserProfile updatedProfile, (err, body, headers) ->
+  client.auth(oauthSession.token, oauthSession.secret).user("sparkleprincesspony").updateUserProfile updatedProfile, (err, body, headers) ->
     console.log err if err
     res.send body.results[0] if body


fix datasets view unit test

fix datasets view unit test

--- test/unit/view/datasets.coffee
+++ test/unit/view/datasets.coffee
@@ -17,10 +17,11 @@

     global.window.app = {navigate: ->}
     $('body').html '

'
+    views = new Backbone.Collection
     models = [
-      new Backbone.Model {_id: 1, name: 'test', displayName: 'Test', boxName: 'sdfsd'}
-      new Backbone.Model {_id: 2, name: 'test2', displayName: 'Test 2', boxName: 'sasdas'}
-      new Backbone.Model {_id: 3, name: 'test3', displayName: 'Test 3', boxName: 'sasisdfdas'}
+      new Backbone.Model {_id: 1, name: 'test', displayName: 'Test', boxName: 'sdfsd', views: views}
+      new Backbone.Model {_id: 2, name: 'test2', displayName: 'Test 2', boxName: 'sasdas', views: views}
+      new Backbone.Model {_id: 3, name: 'test3', displayName: 'Test 3', boxName: 'sasisdfdas', views: views}
     ]

     datasets = new Backbone.Collection models


remove debug logs

remove debug logs

--- jsapp/xlform/src/view.choices.coffee
+++ jsapp/xlform/src/view.choices.coffee
@@ -99,10 +99,8 @@
         @options.cl.options.add(@model)
         @p.html("Option #{1+@options.i}").addClass("preliminary")

-      console.debug('makeEditable', @p)
       $viewUtils.makeEditable @, @model, @p, edit_callback: _.bind @saveValue, @
       @n = $('span', @c)
-      console.debug('makeEditable', @n)
       $viewUtils.makeEditable @, @model, @n, edit_callback: (val) =>
         other_names = @options.cl.getNames()
         if @model.get('name')? && val.toLowerCase() == @model.get('name').toLowerCase()


fix(upload): correctly reload avatar_url after upload, display errors

--- frontend/controllers/ProfileController.coffee
+++ frontend/controllers/ProfileController.coffee
@@ -14,15 +14,20 @@
       for avatar in $scope.avatars
         do (avatar) ->
           Upload.upload({
-                url: '/api/auth/me/avatar',
-                file: avatar
-            }).success (data, status, headers, config) ->
+            url: '/api/auth/me/avatar',
+            file: avatar
+          })
+            .success (data, status, headers, config) ->
               toastr.success "Avatar updated"
               User.get().then (response) ->
                 $scope.user = response.data

                 # hack to force image reloading
                 $scope.user.avatar_url += "?" + Math.random()
+                User.avatar_url = $scope.user.avatar_url
+            .error (data) ->
+              toastr.error "Failed to upload avatar: " +
+                "#{data?.errors?.avatar?.join('; ') ? 'try again later.'}"

     User.get().then (response) ->
       $scope.user = response.data


Activity: clearfix class added sidebar items to be able to use them commonly

--- client/Social/Activity/sidebar/topicitem.coffee
+++ client/Social/Activity/sidebar/topicitem.coffee
@@ -9,6 +9,7 @@
     {name}                  = data
     options.pistachioParams = getSuffix data.participantCount
     options.route           = "Topic/#{name}"
+    options.cssClass        = 'clearfix'

     super options, data


Activity: reset unread count once switched to a tab

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -15,6 +15,15 @@
     @bindChannelEvents()

     @on 'LazyLoadThresholdReached', @bound 'lazyLoad'  if data.typeConstant in ['group', 'topic']
+
+
+  createInputWidget: ->
+
+    return  if @getOption("type") in ['post', 'privatemessage']
+
+    channel = @getData()
+
+    @input = new ActivityInputWidget {channel}

   bindChannelEvents: ->
@@ -41,6 +50,34 @@
     @addSubView @input  if @input
     @addSubView @listController.getView()
     @populate()
+
+
+  show: ->
+
+    super
+
+    KD.utils.wait 1000, @bound 'glance'
+
+
+  glance: ->
+
+    data = @getData()
+    {id, typeConstant} = data
+    {windowController, socialapi} = KD.singletons
+
+    return  unless windowController.focused
+
+    item = @getDelegate().parent.sidebar.selectedItem
+
+    return  unless item.count
+
+    if typeConstant is 'message'
+    then socialapi.channel.glancePinnedPost messageId : id, log
+    else socialapi.channel.updateLastSeenTime channelId : id, log
+
+
+
+

   populate: ->
@@ -97,12 +134,3 @@
     @listController.showLazyLoader()
     @populate()

-
-  createInputWidget: ->
-
-    return  if @getOption("type") in ['post', 'privatemessage']
-
-    channel = @getData()
-
-    @input = new ActivityInputWidget {channel}
-      # .on 'Submit', @listController.bound 'addItem'


Activity: new message/reply scroll-down/up behavior initial implementation

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -23,6 +23,32 @@
     windowController.addFocusListener (focused) =>

       @glance()  if focused and @active
+
+
+    if typeConstant in ['privatemessage', 'post']
+
+      @listController.getListView().once 'ItemWasAdded', =>
+
+        listView = @listController.getListItems().first.commentBox.controller.getListView()
+        listView.on 'ItemWasAdded', @bound 'scrollDown'
+
+    else
+
+      @listController.getListView().on 'ItemWasAdded', @bound 'scrollUp'
+
+
+  scrollDown: ->
+
+    return  unless @active
+
+    KD.utils.defer -> window.scrollTo 0, document.body.scrollHeight
+
+
+  scrollUp: ->
+
+    return  unless @active
+
+    window.scrollTo 0, 0

   createParticipantsView : ->


Activity: messagepane ispageatbottom fix

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -135,11 +135,13 @@

   isPageAtBottom: ->

-    {innerHeight, scrollY} = window
-    {scrollHeight} = document.body
-
-    scrollY + innerHeight >= scrollHeight
-
+    {wrapper}    = @scrollView
+
+    height       = wrapper.getHeight()
+    scrollHeight = wrapper.getScrollHeight()
+    scrollTop    = wrapper.getScrollTop()
+
+    return scrollTop + height >= scrollHeight

   scrollDown: ->


Teamwork: Bottom share warning on share modal is now optional.

--- client/Teamwork/Views/teamworksharemodal.coffee
+++ client/Teamwork/Views/teamworksharemodal.coffee
@@ -2,10 +2,10 @@

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

-    options.cssClass = "tw-modal tw-share-modal"
-    options.title    = "Lorem ipsum dolor title"
-    options.overlay  = yes
-    options.width    = 655
+    options.cssClass         = "tw-modal tw-share-modal"
+    options.overlay          = yes
+    options.width            = 655
+    options.addShareWarning ?= yes

     super options, data

@@ -28,9 +28,10 @@

       inputWidget.on "ActivitySubmitted", => @destroy()

-      @addSubView new KDCustomHTMLView
-        cssClass : "tw-share-warning"
-        partial  : """
-
-
PS: Be warned, this kind of sharing is gonna make your project kinda public so keep that in mind, be caraful, haters gonna hate.

-        """
+      if @getOption "addShareWarning"
+        @addSubView new KDCustomHTMLView
+          cssClass : "tw-share-warning"
+          partial  : """
+
+
Be warned, this makes your VM accessible to others until you close this browser tab. They can see/delete your files.

+          """


remote/machine: change the way we assign shared user instances

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

--- client/app/lib/remote-extensions/machine.coffee
+++ client/app/lib/remote-extensions/machine.coffee
@@ -210,33 +210,34 @@
       .map (user) -> user.instance

-
   reviveUsers: (options, callback = kd.noop) ->

     { storage } = kd.singletons.computeController

-    super options, (err, users = []) =>
-      return callback err  if err
-
-      debug 'reviveUsers', { users, machine: this }
-
-      machineUsers = @getAt('users').map (machineUser) ->
-        # find user instance matching the raw `machineUser` object.
-        userInstance = _.find users, (user) ->
-          user.profile.nickname is machineUser.username
-
-        # if there is a match, update raw `machineUser` object by attaching the
-        # revived instance to it with `instance` key.
-        if userInstance
-          machineUser.instance = userInstance
-
-        return machineUser
+    super options, (err, accounts = []) =>
+      return callback err  if err
+
+      debug 'reviveUsers', { accounts, machine: this }
+
+      machineUsers = @getAt 'users'
+
+      accounts.forEach (account) ->
+        machineUser = _.find machineUsers, (user) ->
+          user.username is account.profile.nickname
+
+        if machineUser
+        then machineUser.instance = account
+        else machineUsers.push {
+          username: account.profile.nickname
+          instance: account
+          permanent: yes
+        }

       @setAt 'users', machineUsers

       storage.machines.push this

-      callback null, users
+      callback null, accounts

   setLabel: (label, callback) ->
@@ -311,15 +312,17 @@

     debug 'unshare user', username

+    { storage } = kd.singletons.computeController
+
     new Promise (resolve, reject) =>
       remote.api.SharedMachine.kick @uid, [username], (err) =>
         promise = @getBaseKite()
           .klientUnshare { username, permanent: yes }
+          .then => @users = @users.filter (user) -> user.username isnt username
           .then => @reviveUsers { permanentOnly : yes }
-          .then -> if err then reject new Error err else resolve()
+          .then =>
+            if err then reject new Error err else resolve()
           .catch reject
-
-

   shareUser: (username) ->

@@ -331,6 +334,7 @@

         @getBaseKite()
           .klientShare { username, permanent: yes }
+          .then => @users.push { approved: no, permanent: yes, username, owner: no }
           .then => @reviveUsers { permanentOnly : yes }
           .then resolve
           .catch reject


More optimizations.

More optimizations.

--- src/PPU.coffee
+++ src/PPU.coffee
@@ -128,7 +128,7 @@
         value

     writeOAMData: (value) ->
-        @primaryOAM[@oamAddress] = value if not @isRenderingInProgress()
+        @primaryOAM[@oamAddress] = value if not @_isRenderingInProgress()
         @oamAddress = (@oamAddress + 1) & 0xFF # Write always increments the address.
         value

@@ -148,19 +148,19 @@
         address

     readData: ->
-        address = @incrementAddress()
+        address = @_incrementAddress()
         oldBufferContent = @vramReadBuffer
         @vramReadBuffer = @_read address                                         # Always increments the address
-        if @isPalleteAddress address then @vramReadBuffer else oldBufferContent # Delayed read outside the pallete memory area
+        if @_isPalleteAddress address then @vramReadBuffer else oldBufferContent # Delayed read outside the pallete memory area

     writeData: (value) ->
-        address = @incrementAddress()                         # Always increments the address
-        @write address, value if not @isRenderingInProgress() # Only during VBLANK or disabled rendering
+        address = @_incrementAddress()                          # Always increments the address
+        @_write address, value if not @_isRenderingInProgress() # Only during VBLANK or disabled rendering
         value

     incrementAddress: ->
         previousAddress = @vramAddress
-        @vramAddress = (@vramAddress + @getAddressIncrement()) & 0xFFFF
+        @vramAddress = (@vramAddress + @_getAddressIncrement()) & 0xFFFF
         previousAddress

     getAddressIncrement: ->
@@ -223,7 +223,7 @@
     incrementFineXScroll: ->
         if @fineXScroll is 7
             @fineXScroll = 0
-            @incrementCoarseXScroll()
+            @_incrementCoarseXScroll()
         else
             @fineXScroll++

@@ -488,6 +488,7 @@
                 baseX = tileX << 3
                 address = ((tileX & 0x10) << 4 | tileY << 4 | tileX & 0x0F) << 4
                 @renderPatternTile baseX, baseY, address
+        undefined

     renderPatternTile: (baseX, baseY, address) ->
         for rowNumber in [0...8]
@@ -501,6 +502,7 @@
                 colorSelect2 = ((patternLayer2Row >> bitPosition) & 0x01) << 1
                 color = @_read 0x3F00 | colorSelect2 | colorSelect1
                 @setFramePixelOnPosition x, y, color
+        undefined

     renderPalettes: ->
         for tileY in [0...4]
@@ -509,11 +511,13 @@
                 baseX = tileX << 5
                 color = @_read 0x3F00 | (tileY << 3) | tileX
                 @renderPaletteTile baseX, baseY, color
+        undefined

     renderPaletteTile: (baseX, baseY, color) ->
         for y in [baseY ... baseY + 28]
             for x in [baseX ... baseX + 32]
-                @setFramePixelOnPosition x, y, color
+                @setFramePixelOnPosition x, y, color
+        undefined

     setFramePixelOnPosition: (x, y, color) ->
         colorPosition = color << 2


LinkViews: fix cosmetic views of headings

--- client/landing/app/CommonViews/LinkViews.coffee
+++ client/landing/app/CommonViews/LinkViews.coffee
@@ -20,8 +20,8 @@
 class FollowedModalView extends KDModalView

   titleMap = ->
-    account : "members"
-    tag     : "topics"
+    account : "Members"
+    tag     : "Topics"

   listControllerMap = ->
     account : MembersListViewController


environments button in book fixed

environments button in book fixed

--- client/landing/app/MainApp/book/bookview.coffee
+++ client/landing/app/MainApp/book/bookview.coffee
@@ -356,7 +356,7 @@
       # make click action
       @clickAnimation()
       # open VM menu
-      @mainView.sidebar.resourcesController.itemsOrdered[0].chevron.$().click()
+      $(".server-container-handler").click()
       # wait 3 sec.
       @utils.wait 2000, =>
         # remove pointer
@@ -367,7 +367,7 @@
     @mainView.once 'transitionend', =>
       # find VM's menu position on footer
       @utils.wait 1000, =>
-        vmMenuOffset = @mainView.sidebar.resourcesController.itemsOrdered[0].chevron.$().offset()
+        vmMenuOffset = $(".server-container-handler").offset()
         # move cursor to VM's menu position
         @pointer.$().offset vmMenuOffset

@@ -379,7 +379,7 @@
       # open VM menu
       # wait 3 sec.
       @utils.wait 2000, =>
-        @mainView.sidebar.resourcesController.itemsOrdered[0].buttonTerm.$().click()
+        $(".server-container-handler").click()
         # remove pointer
         @destroyPointer()

@@ -387,7 +387,7 @@
     @mainView.once 'transitionend', =>
       # find VM's menu position on footer
       @utils.wait 200, =>
-        vmMenuOffset = @mainView.sidebar.resourcesController.itemsOrdered[0].buttonTerm.$().offset()
+        vmMenuOffset = $(".server-container-handler").offset()
         # move cursor to VM's menu position
         @pointer.$().offset vmMenuOffset


Fixed issue with game links. resolves gh-5

--- client/script/controllers/gamecontroller.coffee
+++ client/script/controllers/gamecontroller.coffee
@@ -1,8 +1,6 @@
 define ["ember"],
 	(Ember)->
-		controller = Ember.ObjectController.extend
-			queryParams: ['filter']
-
+		controller = Ember.ObjectController.extend
 			tileStyle: (()->
 				styleValue = ""
 				uri = @get 'titleMediaUri'


Simplify

Simplify

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -153,7 +153,7 @@

   saveState: =>
-    @props.container.dataset.reactState = JSON.stringify state: @state, url: @url
+    @props.container.dataset.reactState = JSON.stringify({@state, @url})
     @componentWillUnmount()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -16,7 +16,7 @@
   # update the player
   player.update()

-  # the player is moved left accord
+  # the player is moved left according to his
   player.I.x
   # => 2.5 # half the player's width


Do Not Violate Basic Rules of QBNs

Do Not Violate Basic Rules of QBNs

Frontal Choices do not lead to pages with side effects. I can't enforce
this, but I can be a good example.

--- src/coffee/game.coffee
+++ src/coffee/game.coffee
@@ -57,6 +57,16 @@

     storylet 'harrowing-carriage',
       'A Harrowing Carriage-Voyage'
+      """
+      This looks unsafe in a profound and painful way.
+      """
+      choice 'ride-carriage',
+        'Do It'
+        'And damn the consequences!'
+        'ride-carriage'
+
+    storylet 'ride-carriage',
+      'A Harrowing Carriage-Voyage'
       (punchiness) ->
         desc = if punchiness > 5 then 'sexy beast' else 'blob'
         increase 'punchiness', -1


Store config filename in config instance

--- src/core/config.coffee
+++ src/core/config.coffee
@@ -47,15 +47,19 @@
         else
           callback new Error "Config file at '#{ path }' does not exist."
     (options, callback) ->
-      callback null, new Config options
+      config = new Config options
+      config.__filename = path
+      callback null, config
   ], callback

 Config.fromFileSync = (path) ->
   ### Read config from *path* as JSON return a Config instance. ###
   if not fileExistsSync path
     throw new Error "Config file at '#{ path }' does not exist."
-  return new Config readJSONSync path
+  config = new Config readJSONSync path
+  config.__filename = path
+  return config

 ### Exports ###

-exports.Config = Config
+module.exports = {Config}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -146,8 +146,6 @@
         if Module.isString?()
           moduleName = Module
           Module = Module.constantize()
-        else
-          # Attempt to discover module name

         unless I.included_modules.include moduleName
           self.extend Module(I, self)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/emitterable.coffee
+++ src/emitterable.coffee
@@ -15,7 +15,7 @@
       acceleration: Point(0, 0.1)
       age: 0
       color: "blue"
-      duration: 30
+      duration: 1.5
       includedModules: ["Movable"]
       height: 2
       maxSpeed: 120


engine data module

engine data module

--- src/engine_data.coffee
+++ src/engine_data.coffee
@@ -1,5 +1,7 @@
 ###*
 The Data module provides methods to store global and persistent data in the engine.
+
+

 @name Data
 @fieldOf Engine