Tutorial update for #109

Tutorial update for #109

--- src/scripts/tutorialSteps.coffee
+++ src/scripts/tutorialSteps.coffee
@@ -148,8 +148,8 @@
       heading: 'Great job!'
       content: [
         'You can use Talk to discuss images with other volunteers if you have questions or find something interesting. Talk will open in a new window - you can just close it to get back to the main classification interface.'
-        'This concludes the tutorial. Now you\'re ready to dive in and complete some classifications on your own!'
         'If you\'re ever unsure of what to mark, you can always consult the field guide on the "About" page for descriptions of the ground covers and species. You can then return to the "Classify" page when you\'re ready.'
+        'This concludes the tutorial. Now you\'re ready to dive in and complete some classifications on your own! Click "No" to move on to your first image.'
       ]
       attach: x: 'right', to: '.talk p', at: x: 'left'
       style: width: 400


Fix new sequelize migration command

Fix new sequelize migration command

--- src/server/utils/cakefile.coffee
+++ src/server/utils/cakefile.coffee
@@ -55,7 +55,7 @@
   @migrate: =>
     command = [".", "node_modules", "salad", "node_modules", ".bin", "sequelize"].join(path.sep)

-    migrate = spawn command, ["--env", Salad.env, "--coffee", "db:migrate"]
+    migrate = spawn command, ["--coffee", "--env", Salad.env, "db:migrate"]

     migrate.stdout.on "data", (data) =>
       console.log data.toString().replace(/\n$/m, '')


Subscribe to editor commands

Subscribe to editor commands

We need to unsubscribe when the editor is removed!

Closes #3651

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -514,8 +514,8 @@
   addCommandListeners: (listenersByCommandName) ->
     {parentView} = @props

-    addListener = (command, listener) ->
-      parentView.command command, (event) ->
+    addListener = (command, listener) =>
+      @subscribe parentView.command command, (event) ->
         event.stopPropagation()
         listener(event)


send current project structure version to docupdater

--- services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
+++ services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
@@ -205,29 +205,32 @@

 	updateProjectStructure : (project_id, userId, changes, callback = (error) ->)->
 		return callback() if !settings.apis.project_history?.sendProjectStructureOps
-
-		docUpdates = DocumentUpdaterHandler._getUpdates('doc', changes.oldDocs, changes.newDocs)
-		fileUpdates = DocumentUpdaterHandler._getUpdates('file', changes.oldFiles, changes.newFiles)
-
-		timer = new metrics.Timer("set-document")
-		url = "#{settings.apis.documentupdater.url}/project/#{project_id}"
-		body =
-			url: url
-			json: { docUpdates, fileUpdates, userId }
-
-		return callback() if (docUpdates.length + fileUpdates.length) < 1
-
-		request.post body, (error, res, body)->
-			timer.done()
-			if error?
-				logger.error {error, url, project_id}, "error update project structure in doc updater"
-				callback(error)
-			else if res.statusCode >= 200 and res.statusCode < 300
-				logger.log {project_id}, "updated project structure in doc updater"
-				callback(null)
-			else
-				logger.error {project_id, url}, "doc updater returned a non-success status code: #{res.statusCode}"
-				callback new Error("doc updater returned a non-success status code: #{res.statusCode}")
+		Project.findOne {_id: project_id}, {version:true}, (err, currentProject) ->
+			return callback(err) if err?
+			return callback new Error("project not found") if !currentProject?
+
+			docUpdates = DocumentUpdaterHandler._getUpdates('doc', changes.oldDocs, changes.newDocs)
+			fileUpdates = DocumentUpdaterHandler._getUpdates('file', changes.oldFiles, changes.newFiles)
+
+			timer = new metrics.Timer("set-document")
+			url = "#{settings.apis.documentupdater.url}/project/#{project_id}"
+			body =
+				url: url
+				json: { docUpdates, fileUpdates, userId, version: currentProject.version }
+
+			return callback() if (docUpdates.length + fileUpdates.length) < 1
+
+			request.post body, (error, res, body)->
+				timer.done()
+				if error?
+					logger.error {error, url, project_id}, "error update project structure in doc updater"
+					callback(error)
+				else if res.statusCode >= 200 and res.statusCode < 300
+					logger.log {project_id}, "updated project structure in doc updater"
+					callback(null)
+				else
+					logger.error {project_id, url}, "doc updater returned a non-success status code: #{res.statusCode}"
+					callback new Error("doc updater returned a non-success status code: #{res.statusCode}")

 	_getUpdates: (entityType, oldEntities, newEntities) ->
 		oldEntities ||= []


:racehorse: Cache line/line-number decorations to avoid per-line queries

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -36,6 +36,7 @@
       @updateHorizontalScrollState()
       @updateScrollbarsState()
       @updateContentState()
+      @updateDecorations()
       @updateLinesState()
       @updateGutterState()
       @updateLineNumbersState()
@@ -43,6 +44,7 @@
     @disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this))
     @disposables.add @model.onDidChangeMini =>
       @updateContentState()
+      @updateDecorations()
       @updateLinesState()
       @updateLineNumbersState()
     @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
@@ -73,6 +75,7 @@
     @updateHorizontalScrollState()
     @updateScrollbarsState()
     @updateContentState()
+    @updateDecorations()
     @updateLinesState()
     @updateCursorsState()
     @updateHighlightsState()
@@ -379,44 +382,18 @@
     return null if @model.isMini()

     decorationClasses = null
-    for markerId, decorations of @model.decorationsForScreenRowRange(row, row) when @model.getMarker(markerId).isValid()
-      for decoration in decorations when decoration.isType('line')
-        properties = decoration.getProperties()
-        range = decoration.getMarker().getScreenRange()
-
-        continue if properties.onlyHead and decoration.getMarker().getHeadScreenPosition().row isnt row
-        continue unless range.intersectsRow(row)
-        if range.isEmpty()
-          continue if properties.onlyNonEmpty
-        else
-          continue if properties.onlyEmpty
-          continue if row is range.end.row and range.end.column is 0
-
-        decorationClasses ?= []
-        decorationClasses.push(properties.class)
-
+    for id, decoration of @lineDecorationsByScreenRow[row]
+      decorationClasses ?= []
+      decorationClasses.push(decoration.getProperties().class)
     decorationClasses

   lineNumberDecorationClassesForRow: (row) ->
     return null if @model.isMini()

     decorationClasses = null
-    for markerId, decorations of @model.decorationsForScreenRowRange(row, row) when @model.getMarker(markerId).isValid()
-      for decoration in decorations when decoration.isType('line-number')
-        properties = decoration.getProperties()
-        range = decoration.getMarker().getScreenRange()
-
-        continue if properties.onlyHead and decoration.getMarker().getHeadScreenPosition().row isnt row
-        continue unless range.intersectsRow(row)
-        if range.isEmpty()
-          continue if properties.onlyNonEmpty
-        else
-          continue if properties.onlyEmpty
-          continue if row is range.end.row and range.end.column is 0
-
-        decorationClasses ?= []
-        decorationClasses.push(properties.class)
-
+    for id, decoration of @lineNumberDecorationsByScreenRow[row]
+      decorationClasses ?= []
+      decorationClasses.push(decoration.getProperties().class)
     decorationClasses

   getCursorBlinkPeriod: -> @cursorBlinkPeriod
@@ -431,6 +408,7 @@
       @scrollTop = scrollTop
       @didStartScrolling()
       @updateVerticalScrollState()
+      @updateDecorations()
       @updateLinesState()
       @updateCursorsState()
       @updateHighlightsState()
@@ -448,6 +426,7 @@
     @state.content.scrollingVertically = false
     if @mouseWheelScreenRow?
       @mouseWheelScreenRow = null
+      @updateDecorations()
       @updateLinesState()
       @updateLineNumbersState()
     else
@@ -478,6 +457,7 @@
       @height = height
       @updateVerticalScrollState()
       @updateScrollbarsState()
+      @updateDecorations()
       @updateLinesState()
       @updateCursorsState()
       @updateHighlightsState()
@@ -492,6 +472,7 @@
       @updateHorizontalScrollState()
       @updateScrollbarsState()
       @updateContentState()
+      @updateDecorations()
       @updateLinesState()

   setBackgroundColor: (backgroundColor) ->
@@ -509,6 +490,7 @@
       @lineHeight = lineHeight
       @updateHeightState()
       @updateVerticalScrollState()
+      @updateDecorations()
       @updateLinesState()
       @updateCursorsState()
       @updateHighlightsState()
@@ -551,6 +533,7 @@
   characterWidthsChanged: ->
     @updateHorizontalScrollState()
     @updateContentState()
+    @updateDecorations()
     @updateLinesState()
     @updateCursorsState()
     @updateHighlightsState()
@@ -616,8 +599,10 @@

   decorationMarkerDidChange: (decoration) ->
     if decoration.isType('line')
+      @updateDecorations()
       @updateLinesState()
     if decoration.isType('line-number')
+      @updateDecorations()
       @updateLineNumbersState()
     if decoration.isType('highlight')
       @updateHighlightsState()
@@ -626,8 +611,10 @@

   didDestroyDecoration: (decoration) ->
     if decoration.isType('line')
+      @updateDecorations()
       @updateLinesState()
     if decoration.isType('line-number')
+      @updateDecorations()
       @updateLineNumbersState()
     if decoration.isType('highlight')
       @updateHighlightsState()
@@ -646,13 +633,57 @@
     @observeDecoration(decoration)

     if decoration.isType('line')
+      @updateDecorations()
       @updateLinesState()
     if decoration.isType('line-number')
+      @updateDecorations()
       @updateLineNumbersState()
     else if decoration.isType('highlight')
       @updateHighlightsState()
     else if decoration.isType('overlay')
       @updateOverlaysState()
+
+  updateDecorations: ->
+    @lineDecorationsByScreenRow = {}
+    @lineNumberDecorationsByScreenRow = {}
+    @highlightDecorationsById = {}
+
+    return if @model.isMini()
+
+    startRow = @computeStartRow()
+    endRow = @computeEndRow()
+    return unless 0 <= startRow <= endRow <= Infinity
+
+    for markerId, decorations of @model.decorationsForScreenRowRange(startRow, endRow)
+      marker = @model.getMarker(markerId)
+      continue unless marker.isValid()
+
+      range = marker.getScreenRange()
+      for decoration in decorations
+        if decoration.isType('line') or decoration.isType('line-number')
+          @updateLineDecorationCaches(decoration, range)
+
+  updateLineDecorationCaches: (decoration, range) ->
+    marker = decoration.getMarker()
+    properties = decoration.getProperties()
+
+    if range.isEmpty()
+      return if properties.onlyNonEmpty
+    else
+      return if properties.onlyEmpty
+      omitLastRow = range.end.column is 0
+
+    for row in [range.start.row..range.end.row] by 1
+      continue if properties.onlyHead and row isnt marker.getHeadScreenPosition().row
+      continue if omitLastRow and row is range.end.row
+
+      if decoration.isType('line')
+        @lineDecorationsByScreenRow[row] ?= {}
+        @lineDecorationsByScreenRow[row][decoration.id] = decoration
+
+      if decoration.isType('line-number')
+        @lineNumberDecorationsByScreenRow[row] ?= {}
+        @lineNumberDecorationsByScreenRow[row][decoration.id] = decoration

   observeCursor: (cursor) ->
     didChangePositionDisposable = cursor.onDidChangePosition =>


fixes for PR and IR

fixes for PR and IR

--- shared/IdleController.coffee
+++ shared/IdleController.coffee
@@ -277,11 +277,11 @@
         _.sortBy (_.keys player.statistics or []), (v) -> v

       $scope.itemItemScore = (item) ->
-        return null if not item._baseScore or not item._calcScore
+        return 0 if not item._baseScore or not item._calcScore
         parseInt (item._calcScore / item._baseScore) * 100

       $scope.playerItemScore = (player, item) ->
-        return null if not item._calcScore or not player._baseStats.itemFindRange
+        return 0 if not item._calcScore or not player._baseStats.itemFindRange
         parseInt (item._calcScore / player._baseStats.itemFindRange) * 100

       gauge = null
@@ -422,4 +422,4 @@
         return if newVal is oldVal
         $scope.filters = newVal
       , yes
-  ]+  ]


:fire: Remove deprecated code

:fire: Remove deprecated code

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -27,8 +27,6 @@
   updatesPaused: false
   updateRequestedWhilePaused: false
   heightAndWidthMeasurementRequested: false
-  cursorMoved: false
-  selectionChanged: false
   inputEnabled: true
   measureScrollbarsWhenShown: true
   measureLineHeightAndDefaultCharWidthWhenShown: true
@@ -118,11 +116,6 @@
   updateSync: ->
     @oldState ?= {}
     @newState = @presenter.getState()
-
-    cursorMoved = @cursorMoved
-    selectionChanged = @selectionChanged
-    @cursorMoved = false
-    @selectionChanged = false

     if @editor.getLastSelection()? and not @editor.getLastSelection().isEmpty()
       @domNode.classList.add('has-selection')
@@ -219,8 +212,6 @@

   observeEditor: ->
     @disposables.add @editor.observeGrammar(@onGrammarChanged)
-    @disposables.add @editor.observeCursors(@onCursorAdded)
-    @disposables.add @editor.observeSelections(@onSelectionAdded)

   listenForDOMEvents: ->
     @domNode.addEventListener 'mousewheel', @onMouseWheel
@@ -486,29 +477,6 @@
     @sampleBackgroundColors()
     @remeasureCharacterWidths()

-  onSelectionAdded: (selection) =>
-    selectionDisposables = new CompositeDisposable
-    selectionDisposables.add selection.onDidChangeRange => @onSelectionChanged(selection)
-    selectionDisposables.add selection.onDidDestroy =>
-      @onSelectionChanged(selection)
-      selectionDisposables.dispose()
-      @disposables.remove(selectionDisposables)
-
-    @disposables.add(selectionDisposables)
-
-    if @editor.selectionIntersectsVisibleRowRange(selection)
-      @selectionChanged = true
-
-  onSelectionChanged: (selection) =>
-    if @editor.selectionIntersectsVisibleRowRange(selection)
-      @selectionChanged = true
-
-  onCursorAdded: (cursor) =>
-    @disposables.add cursor.onDidChangePosition @onCursorMoved
-
-  onCursorMoved: =>
-    @cursorMoved = true
-
   handleDragUntilMouseUp: (dragHandler) =>
     dragging = false
     lastMousePosition = {}


update generator copied files and tasks

update generator copied files and tasks

--- app/generator.coffee
+++ app/generator.coffee
@@ -98,36 +98,35 @@
         )

       [
+        { from: ".agignore", to: ".agignore" },
         { from: ".babelrc.js", to: ".babelrc.js" },
         { from: ".editorconfig", to: ".editorconfig" },
         { from: ".eslintrc.json", to: ".eslintrc.json" },
-        { from: ".agignore", to: ".agignore" },
         { from: ".gitignore", to: ".gitignore" },
         { from: ".gitignore_pw", to: "src/.gitignore" },
         { from: ".gitkeep", to: "database/dev/.gitkeep" },
         { from: ".gitkeep", to: "database/prod/.gitkeep" },
         { from: ".prettierrc", to: ".prettierrc" },
         { from: ".tmuxinator.yml.example", to: ".tmuxinator.yml.example" },
-	{ from: "device-icon-template.png", to: "src/device-icon-template.png" },
+        { from: ".tmuxinator.yml.example", to: ".tmuxinator.yml" },
+        { from: "device-icon-template.png", to: "src/device-icon-template.png" },
+        { from: "docker", to: "docker" },
+        { from: "docker-compose.yml", to: "docker-compose.yml" },
+        { from: "gulp", to: "gulp" },
         { from: "gulpfile.js", to: "gulpfile.js" },
-        { from: "package.json", to: "package.json" },
-        { from: "setup.js", to: "setup.js" },
-        { from: "gulp/secrets-sample.js", to: "gulp/secrets.js"},
         { from: "package.json", to: "package.json" },
         { from: "setup.js", to: "setup.js"},
         { from: "webpack.config.base.js", to: "webpack.config.base.js"},
         { from: "webpack.config.dev.js", to: "webpack.config.dev.js"},
         { from: "webpack.config.prod.js", to: "webpack.config.prod.js"},
-        { from: "gulp", to: "gulp" },
-        { from: "docker", to: "docker" },
-        { from: "docker-compose.yml", to: "docker-compose.yml" },
       ].map(copyFile)

-      @template "_robots.txt",              "src/robots.txt"
+      @template "_robots.txt"                      , "src/robots.txt"
       @template "_.env.example"                    , ".env.example"
       @template "_.env.example"                    , ".env"
       @template "docker/apache/_local.apache.conf" , "docker/apache/local.apache.conf"

+      shell.rm "-f", "docker/apache/_local.apache.conf"

@@ -195,6 +194,8 @@
       @copy ".gitkeep", "src/site/assets/sessions/.gitkeep"
       @copy ".gitkeep", "src/site/assets/files/.gitkeep"
       @copy ".gitkeep", "src/site/assets/logs/.gitkeep"
+      @copy ".gitkeep", "src/site/templates/assets/img/raw/.gitkeep"
+      @copy ".gitkeep", "src/site/templates/assets/img/raw/svg/inline-icons/.gitkeep"

       # setup for ProcessWire install
       at dest('src/'), () =>
@@ -231,13 +232,19 @@
     #   $STYLEGUIDE
     #*------------------------------------*/
     setupStyleguide = () =>
-      @log.info "run `npx -p @storybook/cli sb init` to initialise styleguide"
       styleguideFolder = 'styleguide'
       mkdirp styleguideFolder
       shell.pushd styleguideFolder
       shell.popd()

-      @log.ok('OK')
+      shell.exec 'npm install', (code, stdout, stderr) ->
+        console.log('Exit code:', code)
+        console.log('Program output:', stdout)
+
+        if stderr then console.log('Program stderr:', stderr)
+
+      @log.ok('OK')
+

@@ -261,6 +268,25 @@
     #*------------------------------------*/
     setupGit = () =>
       GitUtils.init(dest())
+
+
+
+
+
+    #*------------------------------------*\
+    #   $INSTALL NODE MODULES
+    #*------------------------------------*/
+    installNodeModules = () =>
+      @log.ok('Installing node modules...')
+
+      shell.exec 'npm install', (code, stdout, stderr) ->
+        console.log('Exit code:', code)
+        console.log('Program output:', stdout)
+
+        if stderr then console.log('Program stderr:', stderr)
+
+      @log.ok('OK')
+

@@ -275,3 +301,4 @@
     setupCSSFramework()
     setupGit()
     setupStyleguide()
+    installNodeModules()


Optionally colorize periods

Optionally colorize periods

--- app/lib/chart.coffee
+++ app/lib/chart.coffee
@@ -49,6 +49,8 @@
       .attr('width', @width)
       .attr('height', @height)
       # .attr('clip-path', 'url(#clip)')
+
+    @colors = d3.scale.category10()

     if @parent
       @rawData = @parent.rawData
@@ -133,6 +135,11 @@
     chartRegion.attr('class', 'dot')
       .attr('cx', @x)
       .attr('cy', @y)
+      .style 'fill', (d, i) =>
+        if @period and @colorize
+          @colors Math.floor(d.x / @period)
+        else
+          'black'

   render: =>
     xExtent = d3.extent @data, (d) -> d.x


fix types, closes #89

fix types, closes #89

--- app/lib/en_us.coffee
+++ app/lib/en_us.coffee
@@ -239,7 +239,7 @@

NASA Jet Propulsion Laboratory has a great collection of education resources relating the the Mars Reconnaissance Orbiter. It's also worth checking out NASA's wavelength, this site allows you to search for resources that relate to Mars across a ranges of Martian missions.

-
The Zooniverse has also recently launched ZooTeach where educators can share resources relating to Planet Four and the other citizen science projects. Planet Four is a recent addition, so if you have any idea's for how to use the project in the classroom, it would be great if you could share your lesson idea's or resources!

+
The Zooniverse has also recently launched ZooTeach where educators can share resources relating to Planet Four and the other citizen science projects. Planet Four is a recent addition, so if you have any ideas for how to use the project in the classroom, it would be great if you could share your lesson ideas or resources!

How can I keep up to date with education and Planet Four?


Updated field guide

Updated field guide

--- app/lib/guide.coffee
+++ app/lib/guide.coffee
@@ -111,8 +111,8 @@
     {
       header: 'other (non-primate)'
       subHeader: null
-      description: 'Mark any animal that does not fall into the above categories as "other."'
-      exampleImages: ["./assets/guide/other-1.jpg", "./assets/guide/other-2.jpg"]
+      description: 'Mark any animal that does not fall into the above categories as "other." This includes cat-like viverrids like the civet and genet.'
+      exampleImages: ["./assets/guide/other-1.jpg", "./assets/guide/other-2.jpg", "./assets/guide/other-3.jpg", "./assets/guide/other-4.jpg"]
     },

     {
@@ -187,7 +187,7 @@
     {
       header: 'small cat'
       subHeader: 'Felinae subfamily'
-      description: 'Several species of small felines can be found in Africa, including the caracal, the serval, the African golden cat, and the African wildcat. Any cat smaller than a leopard or a cheetah can be classified as a "small cat."'
+      description: 'Several species of small felines can be found in Africa, including the caracal, the serval, the African golden cat, and the African wildcat. Any cat smaller than a leopard or a cheetah can be classified as a "small cat." Please note that viverrids, like civets and genets, are not cats, and should be marked as "other."'
       exampleImages: ["./assets/guide/small-cat-1.jpg", "./assets/guide/small-cat-2.jpg", "./assets/guide/small-cat-3.jpg"]
     },


Added a resource string and fixed other

Added a resource string and fixed other

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -1364,12 +1364,13 @@
     course: "Course"
     courses: "Courses"
     course_instance: "Course Instance"
-    courses_instances: "Course Instances"
+    course_instances: "Course Instances"
     classroom: "Classroom"
     classrooms: "Classrooms"
     clan: "Clan"
     clans: "Clans"
     members: "Members"
+    users: "Users"

   concepts:
     advanced_strings: "Advanced Strings"


Update ja.coffee

Update ja.coffee

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -724,16 +724,16 @@
     wrong_password: "間違ったパスワード"
     upload_picture: "画像をアップロード"
     delete_this_account: "アカウントを完全削除する"
-#    reset_progress_tab: "Reset All Progress"
-#    reset_your_progress: "Clear all your progress and start over"
+    reset_progress_tab: "すべての進捗をリセットする"
+    reset_your_progress: "すべての進捗をリセットしやり直す"
     god_mode: "ゴッドモード"
     password_tab: "パスワード"
     emails_tab: "メール"
     admin: "管理者"
     new_password: "新パスワード"
     new_password_verify: "新パスワードを再入力"
-    type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい"
-#    type_in_email_progress: "Type in your email to confirm deleting your progress."
+    type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい。"
+    type_in_email_progress: "進捗を消すために、メールアドレスを入力してください。"
     type_in_password: "そして、パスワードを入力してください。"
     email_subscriptions: "ニュースレターの購読"
     email_subscriptions_none: "No Email Subscriptions."
@@ -1147,22 +1147,22 @@
     summary_wins: " 勝利数, "
     summary_losses: " 敗北数"
     rank_no_code: "新しいコードがランクにありません"
-#    rank_my_game: "Rank My Game!"
+    rank_my_game: "試合をランキングに送信!"
     rank_submitting: "送信中..."
     rank_submitted: "ランキングに送信されました。"
-#    rank_failed: "Failed to Rank"
-#    rank_being_ranked: "Game Being Ranked"
+    rank_failed: "ランキングに送信できませんでした。"
+    rank_being_ranked: "ランキングにのっています"
 #    rank_last_submitted: "submitted "
 #    help_simulate: "Help simulate games?"
 #    code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
 #    no_ranked_matches_pre: "No ranked matches for the "
 #    no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
-#    choose_opponent: "Choose an Opponent"
+    choose_opponent: "相手を選んでください"
     select_your_language: "使う言語を選んでください！"
     tutorial_play: "チュートリアルで遊ぶ"
     tutorial_recommended: "はじめて遊ぶ人におすすめ"
     tutorial_skip: "チュートリアルをスキップする"
-#    tutorial_not_sure: "Not sure what's going on?"
+    tutorial_not_sure: "なにが起きているのかわかりませんか？"
     tutorial_play_first: "はじめからチュートリアルを遊ぶ"
     simple_ai: "単純なAI"
     warmup: "ウォームアップ"


Update ja.coffee

Update ja.coffee

Deleted # from the lines I translated.

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -587,35 +587,35 @@
     more_info_3: "でCodeCombatを使っている教育者の仲間とつながりましょう。"

   teachers_survey:
-#    title: "教師用アンケート"
-#    must_be_logged: "最初にログインしてください。アカウントを作成するか、上記のメニューからログインしてください。"
-#    retrieving: "情報を取得しています..."
-#    being_reviewed_1: "無料トライアルの申し込みが"
-#    being_reviewed_2: "レビューされています。"
-#    approved_1: "無料トライアルの申し込みが"
-#    approved_2: "承認されました!"
-#    approved_4: "以下のページで生徒を入会させることができます："
-#    approved_5: "コース"
-#    approved_6: "ページ。"
-#    denied_1: "無料トライアルの申し込みが"
-#    denied_2: "拒否されました。"
-#    contact_1: "お問い合わせください"
-#    contact_2: "ご質問がある場合は"
-#    description_1: "教師の方に無料トライアルを提供しています。有料コースで生徒さんを入会させるのに使用できる、2つの無料入会権が与えられます。"
-#    description_1b: "詳細は以下のページで参照してください："
-#    description_2: "教師用"
-#    description_3: "ページ。"
-#    description_4: "この簡易アンケートに記入をお願いします。設定手順をメールにてお送りします。"
-#    email: "電子メールアドレス"
+    title: "教師用アンケート"
+    must_be_logged: "最初にログインしてください。アカウントを作成するか、上記のメニューからログインしてください。"
+    retrieving: "情報を取得しています..."
+    being_reviewed_1: "無料トライアルの申し込みが"
+    being_reviewed_2: "レビューされています。"
+    approved_1: "無料トライアルの申し込みが"
+    approved_2: "承認されました!"
+    approved_4: "以下のページで生徒を入会させることができます："
+    approved_5: "コース"
+    approved_6: "ページ。"
+    denied_1: "無料トライアルの申し込みが"
+    denied_2: "拒否されました。"
+    contact_1: "お問い合わせください"
+    contact_2: "ご質問がある場合は"
+    description_1: "教師の方に無料トライアルを提供しています。有料コースで生徒さんを入会させるのに使用できる、2つの無料入会権が与えられます。"
+    description_1b: "詳細は以下のページで参照してください："
+    description_2: "教師用"
+    description_3: "ページ。"
+    description_4: "この簡易アンケートに記入をお願いします。設定手順をメールにてお送りします。"
+    email: "電子メールアドレス"
     school: "学校名"
     location: "市町村"
-#    age_students: "生徒さんの年齢を教えてください。"
-#    under: "未満"
-#    other: "その他:"
-#    amount_students: "何人の生徒さんをお持ちですか？"
-#    hear_about: "CodeCombatをどのようにお知りなりましたか?"
-#    fill_fields: "全てのフィールドを記入してください。"
-#    thanks: "ありがとうございます。設定の手順を追ってお送りします。"
+    age_students: "生徒さんの年齢を教えてください。"
+    under: "未満"
+    other: "その他:"
+    amount_students: "何人の生徒さんをお持ちですか？"
+    hear_about: "CodeCombatをどのようにお知りなりましたか?"
+    fill_fields: "全てのフィールドを記入してください。"
+    thanks: "ありがとうございます。設定の手順を追ってお送りします。"

   versions:
     save_version_title: "新しいバージョンをセーブ"
@@ -623,9 +623,9 @@
     submitting_patch: "パッチを送信中..."
     cla_prefix: "変更を適用するには, 私達のCLAに同意する必要があります。"
     cla_url: "CLA"
-#    cla_suffix: "。"
+    cla_suffix: "。"
     cla_agree: "同意する"
-#    owner_approve: "ユーザーは変更が実際に表示される前に同意する必要があります。"
+    owner_approve: "ユーザーは変更が実際に表示される前に同意する必要があります。"

   contact:
     contact_us: "お問い合わせ"
@@ -660,7 +660,7 @@
     password_tab: "パスワード"
     emails_tab: "メール"
     admin: "管理者"
-#    manage_subscription: "購読を管理するにはここをクリックしてください。"
+    manage_subscription: "購読を管理するにはここをクリックしてください。"
     new_password: "新パスワード"
     new_password_verify: "新パスワードを再入力"
     type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい。"
@@ -671,12 +671,12 @@
     email_announcements: "お知らせ"
     email_announcements_description: "CodeCombatの最新のニュースや進展をメールで受け取る"
     email_notifications: "通知"
-#    email_notifications_summary: "CodeCombatアクティビティに関する自動電子メール通知の管理。"
-#    email_any_notes: "すべての通知"
-#    email_any_notes_description: "アクティビティすべての通知メールを停止することを無効にします。"
+    email_notifications_summary: "CodeCombatアクティビティに関する自動電子メール通知の管理。"
+    email_any_notes: "すべての通知"
+    email_any_notes_description: "アクティビティすべての通知メールを停止することを無効にします。"
     email_news: "ニュース"
     email_recruit_notes: "求人"
-#    email_recruit_notes_description: "上手にプレイしてくれる場合、（更に良い）仕事を紹介するためにこちらから連絡を差し上げる場合があります。"
+    email_recruit_notes_description: "上手にプレイしてくれる場合、（更に良い）仕事を紹介するためにこちらから連絡を差し上げる場合があります。"
     contributor_emails: "開発を手伝ってくれる人向けのメール"
     contribute_prefix: "私達は開発を手伝ってくれる人を探しています。 詳しくは "
     contribute_page: "こちらのページ"


fix ref to StaticQuotes

fix ref to StaticQuotes

--- src/scripts/mitch-hedberg.coffee
+++ src/scripts/mitch-hedberg.coffee
@@ -65,7 +65,7 @@
 		msg.send quote
 	else
 		keep_it_clean msg, quote, (body, err) ->
-			msg.send staticquotes[Math.floor(Math.random() * staticquotes.length)] if err
+			msg.send StaticQuotes[Math.floor(Math.random() * staticquotes.length)] if err
 			#because potty word just sounds funny
 			msg.send body.getElementsByTagName("CleanText")[0].firstChild.nodeValue.replace /(Explicit)+/g, "potty word"


Update ro.coffee

Update ro.coffee

Almost done.
The legal stuff is the most boring thing i ever had to translate...

--- app/locale/ro.coffee
+++ app/locale/ro.coffee
@@ -265,7 +265,7 @@
     message: "Mesaj"

   about:
-    who_is_codecombat: "Cine este CodeCombat?"  # I assume you meant (what)
+    who_is_codecombat: "Cine este CodeCombat?"
     why_codecombat: "De ce CodeCombat?"
     who_description_prefix: "au pornit împreuna CodeCombat în 2013. Tot noi am creat "
     who_description_suffix: "în 2008, dezvoltând aplicația web si iOS #1 de învățat cum să scri caractere Japoneze si Chinezești."
@@ -277,77 +277,77 @@
     why_paragraph_3_center: "ci"
     why_paragraph_3_italic_caps: "TREBUIE SĂ TERMIN ACEST NIVEL!"
     why_paragraph_3_suffix: "De aceea CodeCombat este un joc multiplayer, nu un curs transfigurat în joc. Nu ne vom opri până când tu nu te poți opri--și de data asta, e de bine."
-#    why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
-#    why_ending: "And hey, it's free. "
-#    why_ending_url: "Start wizarding now!"
-#    george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
-#    scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
-#    nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
-#    jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
-#    michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
-
-#  legal:
-#    page_title: "Legal"
-#    opensource_intro: "CodeCombat is free to play and completely open source."
-#    opensource_description_prefix: "Check out "
-#    github_url: "our GitHub"
-#    opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See "
-#    archmage_wiki_url: "our Archmage wiki"
-#    opensource_description_suffix: "for a list of the software that makes this game possible."
-#    practices_title: "Respectful Best Practices"
-#    practices_description: "These are our promises to you, the player, in slightly less legalese."
-#    privacy_title: "Privacy"
-#    privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent."
-#    security_title: "Security"
-#    security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
-#    email_title: "Email"
-#    email_description_prefix: "We will not inundate you with spam. Through"
-#    email_settings_url: "your email settings"
-#    email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
-#    cost_title: "Cost"
-#    cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
-#    recruitment_title: "Recruitment"
-#    recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not just in the game, but also in real life."
-#    url_hire_programmers: "No one can hire programmers fast enough"
-#    recruitment_description_suffix: "so once you've sharpened your skills and if you agree, we will demo your best coding accomplishments to the thousands of employers who are drooling for the chance to hire you. They pay us a little, they pay you"
-#    recruitment_description_italic: "a lot"
-#    recruitment_description_ending: "the site remains free and everybody's happy. That's the plan."
-#    copyrights_title: "Copyrights and Licenses"
-#    contributor_title: "Contributor License Agreement"
-#    contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"
-#    cla_url: "CLA"
-#    contributor_description_suffix: "to which you should agree before contributing."
-#    code_title: "Code - MIT"
-#    code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
-#    mit_license_url: "MIT license"
-#    code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
-#    art_title: "Art/Music - Creative Commons "
-#    art_description_prefix: "All common content is available under the"
-#    cc_license_url: "Creative Commons Attribution 4.0 International License"
-#    art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
-#    art_music: "Music"
-#    art_sound: "Sound"
-#    art_artwork: "Artwork"
-#    art_sprites: "Sprites"
-#    art_other: "Any and all other non-code creative works that are made available when creating Levels."
-#    art_access: "Currently there is no universal, easy system for fetching these assets. In general, fetch them from the URLs as used by the site, contact us for assistance, or help us in extending the site to make these assets more easily accessible."
-#    art_paragraph_1: "For attribution, please name and link to codecombat.com near where the source is used or where appropriate for the medium. For example:"
-#    use_list_1: "If used in a movie or another game, include codecombat.com in the credits."
-#    use_list_2: "If used on a website, include a link near the usage, for example underneath an image, or in a general attributions page where you might also mention other Creative Commons works and open source software being used on the site. Something that's already clearly referencing CodeCombat, such as a blog post mentioning CodeCombat, does not need some separate attribution."
-#    art_paragraph_2: "If the content being used is created not by CodeCombat but instead by a user of codecombat.com, attribute them instead, and follow attribution directions provided in that resource's description if there are any."
-#    rights_title: "Rights Reserved"
-#    rights_desc: "All rights are reserved for Levels themselves. This includes"
-#    rights_scripts: "Scripts"
-#    rights_unit: "Unit configuration"
-#    rights_description: "Description"
-#    rights_writings: "Writings"
-#    rights_media: "Media (sounds, music) and any other creative content made specifically for that Level and not made generally available when creating Levels."
-#    rights_clarification: "To clarify, anything that is made available in the Level Editor for the purpose of making levels is under CC, whereas the content created with the Level Editor or uploaded in the course of creation of Levels is not."
-#    nutshell_title: "In a Nutshell"
-#    nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
-#    canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
-
-#  contribute:
+    why_paragraph_4: "Dacă e să devi dependent de vreun joc, devino dependent de acesta și fi un vrăjitor al noii ere tehnologice."
+    why_ending: "Nu uita, este totul gratis. "
+    why_ending_url: "Devino un vrăjitor acum!"
+    george_description: "CEO, business guy, web designer, game designer, și campion al programatorilor începători."
+    scott_description: "Programmer extraordinaire, software architect, kitchen wizard, și  maestru al finanțelor. Scott este cel rezonabil."
+    nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick poate să facă orice si a ales să dezvolte CodeCombat."
+    jeremy_description: "Customer support mage, usability tester, and community organizer; probabil ca ați vorbit deja cu Jeremy."
+    michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael este cel care ține serverele in picioare."
+
+  legal:
+    page_title: "Aspecte Legale"
+    opensource_intro: "CodeCombat este free-to-play și complet open source."
+    opensource_description_prefix: "Vizitează "
+    github_url: "pagina noastră de GitHub"
+    opensource_description_center: "și ajută-ne dacă îți place! CodeCombat este construit peste o mulțime de proiecte open source, care noi le iubim. Vizitați"
+    archmage_wiki_url: "Archmage wiki"
+    opensource_description_suffix: "pentru o listă cu software-ul care face acest joc posibil."
+#    practices_title: "Respectful Best Practices"   #not sure what you mean here? other word for /practices/?
+    practices_description: "Acestea sunt promisiunile noastre către tine, jucătorul, fără așa mulți termeni legali."
+    privacy_title: "Confidenţialitate şi termeni"
+    privacy_description: "Noi nu vom vinde nici o informație personală. Intenționăm să obținem profit prin recrutare eventual, dar stați liniștiți , nu vă vom vinde informațiile personale companiilor interesate fără consimțământul vostru explicit."
+    security_title: "Securitate"
+    security_description: "Ne străduim să vă protejăm informațiile personale. Fiind un proiect open-source, site-ul nostru oferă oricui posibilitatea de a ne revizui și îmbunătăți sistemul de securitate."
+    email_title: "Email"
+    email_description_prefix: "Noi nu vă vom inunda cu spam. Prin"
+    email_settings_url: "setările tale de email"
+    email_description_suffix: " sau prin link-urile din email-urile care vi le trimitem, puteți să schimbați preferințele și să vâ dezabonați oricând."
+    cost_title: "Cost"
+    cost_description: "Momentan, CodeCombat este 100% gratis! Unul dintre obiectele noastre principale este să îl menținem așa, astfel încât să poată juca cât mai mulți oameni. Dacă va fi nevoie , s-ar putea să percepem o plată pentru o pentru anumite servici,dar am prefera să nu o facem. Cu puțin noroc, vom putea susține compania cu:"
+    recruitment_title: "Recrutare"
+    recruitment_description_prefix: "Aici la CodeCombat, vei deveni un vrăjitor puternic nu doar în joc , ci și în viața reală."
+    url_hire_programmers: "Nimeni nu poate angaja programatori destul de rapid"
+    recruitment_description_suffix: "așa că odată ce ți-ai dezvoltat abilitățile și esti de acord, noi vom trimite un demo cu cele mai bune realizări ale tale către miile de angajatori care se omoară să pună mâna pe tine. Pe noi ne plătesc puțin, pe tine te vor plăti"
+    recruitment_description_italic: "mult"
+    recruitment_description_ending: "site-ul rămâne gratis și toată lumea este fericită. Acesta este planul."
+    copyrights_title: "Drepturi de autor și licențe"
+    contributor_title: "Acord de licență Contributor"
+    contributor_description_prefix: "Toți contribuitorii, atât pe site cât și pe GitHub-ul nostru, sunt supuși la"
+    cla_url: "ALC"
+    contributor_description_suffix: "la care trebuie să fi de accord înainte să poți contribui."
+    code_title: "Code - MIT"
+    code_description_prefix: "Tot codul deținut de CodeCombat sau hostat pe codecombat.com, atât pe GitHub cât și în baza de date codecombat.com, este licențiată sub"
+    mit_license_url: "MIT license"
+    code_description_suffix: "Asta include tot codul din Systems și Components care este oferit de către CodeCombat cu scopul de a crea nivele."
+    art_title: "Artă/Muzică - Conținut Comun "
+    art_description_prefix: "Tot conținutul creativ/artistic este valabil sub"
+    cc_license_url: "Creative Commons Attribution 4.0 International License"
+    art_description_suffix: "Conținut comun este orice făcut general valabil de către CodeCombat cu scopul de a crea nivele. Asta include:"
+    art_music: "Muzică"
+    art_sound: "Sunet"
+    art_artwork: "Artwork"
+    art_sprites: "Sprites"  #can t be translated, either suggest alternative name or must be left like this
+    art_other: "Orice si toate celelalte creații non-cod care sunt disponibile când se crează nivele."
+    art_access: "Momentan nu există nici un sistem universal,ușor pentru preluarea acestor bunuri. În general, preluați-le precum site-ul din URL-urile folosite, contactați-ne pentru asistență, sau ajutați-ne sa extindem site-ul pentru a face aceste bunuri mai ușor accesibile."
+    art_paragraph_1: "Pentru atribuire, vă rugăm numiți și lăsați referire link la codecombat.com unde este folosită sursa  sau unde este adecvat pentru mediu. De exemplu:"
+    use_list_1: "Dacă este folosit într-un film sau alt joc, includeți codecombat.com la credite."
+    use_list_2: "Dacă este folosit pe un site, includeți un link in apropiere, de exemplu sub o imagine, sau in pagina generală de atribuiri unde menționați și alte Bunuri Creative și software open source folosit pe site. Ceva care face referință explicit la CodeCombat, precum o postare pe un blog care menționează CodeCombat, nu trebuie să facă o atribuire separată."
+    art_paragraph_2: "Dacă conținutul folosit nu este creat de către CodeCombat ci de către un utilizator al codecombat.com,atunci faceți referință către ei, și urmăriți indicațiile de atribuire prevăzute în descrierea resursei dacă există."
+    rights_title: "Drepturi rezervate"
+    rights_desc: "Toate drepturile sunt rezervate pentru Nivele în sine. Asta include"
+    rights_scripts: "Script-uri"
+    rights_unit: "Configurații de unități"
+    rights_description: "Descriere"
+    rights_writings: "Scrieri"
+    rights_media: "Media (sunete, muzică) și orice alt conținut creativ dezvoltat special pentru acel nivel care nu este valabil în mod normal pentru creat nivele."
+    rights_clarification: "Pentru a clarifica, orice este valabil in Editorul de Nivele pentru scopul de a crea nivele se află sub CC,pe când conținutul creat cu Editorul de Nivele sau încărcat pentru a face nivelul nu se află." #CC stands for...?
+    nutshell_title: "Pe scurt"
+    nutshell_description: "Orice resurse vă punem la dispoziție în Editorul de Nivele puteți folosi liber cum vreți pentru a crea nivele. Dar ne rezervăm dreptul de a rezerva distribuția de nivele în sine (care sunt create pe codecombat.com) astfel încât să se poată percepe o taxă pentru ele pe vitor, dacă se va ajunge la așa ceva."
+    canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
+
+  contribute:
 #    page_title: "Contributing"
 #    character_classes_title: "Character Classes"
 #    introduction_desc_intro: "We have high hopes for CodeCombat."


OrderItemFormView overrides `itemSelected`

`itemSelected` is handled on this view. It creates a new `orderItem`
record and assigns it to `parentView`’s model

--- app/assets/javascripts/views/order_items/order_item_form_view.js.coffee
+++ app/assets/javascripts/views/order_items/order_item_form_view.js.coffee
@@ -1,2 +1,11 @@
-Bandango.OrderItemFormView = Ember.View.extend
+Bandango.OrderItemFormView = Ember.View.extend Bandango.ItemOnFormMixin,
   templateName: "order_items/form"
+  tagName: "tr"
+
+  itemSelected: (itemObject) ->
+    store = @get("controller.store")
+    store.find("item", itemObject.id).then (item) =>
+      # item = store.typeMapFor(Bandango.Item).idToRecord[itemObject.id]   # 'dirty'
+      orderItem = store.createRecord("orderItem", cantidad: 1, item: item)
+      @get("parentView.model").get("orderItems").addObject orderItem
+      $(".item_textfield").val null


Small fix for css classes

Small fix for css classes

--- plugins/image.coffee
+++ plugins/image.coffee
@@ -284,7 +284,7 @@
                             tmpObject.parent("div").addClass "tmpBig inlineImage-" + position
                         else
                             $(".tmpBig").replaceWith $(".tmp, editable")  if $(".tmpBig")
-                            tmpObject.addClass("inlineImage-" + position).css "display", "block"
+                            tmpObject.removeClass("inlineImage-middle inlineImage-left inlineImage-right").addClass("inlineImage-" + position).css "display", "block"
                         helper.showOverlay position
                     helper.delayAction createTmpObject, 300

@@ -310,14 +310,15 @@
                     helper.removeTmpNodes()
                     position = helper.calcPosition(ui, offset)
                     imageInsert = helper.createInsertElement(ui, false)
+
                     if position is "middle"
                         imageInsert.css('display', 'block')
-                        imageInsert.addClass("inlineImage-" + position).css
+                        imageInsert.removeClass("inlineImage-middle inlineImage-left inlineImage-right").addClass("inlineImage-" + position).css
                           position: "relative"
                           left: ((editable.width() + parseFloat(editable.css('paddingLeft')) + parseFloat(editable.css('paddingRight'))) - imageInsert.attr('width')) / 2
                         imageInsert.insertBefore $(event.target)
                     else
-                        imageInsert.addClass("inlineImage-" + position).css "display", "block"
+                        imageInsert.removeClass("inlineImage-middle inlineImage-left inlineImage-right").addClass("inlineImage-" + position).css "display", "block"
                         $(event.target).prepend imageInsert

                     overlay.big.hide()


think I need this too

think I need this too

--- app/assets/javascripts/views/_shared/chart_totals_view.js.coffee
+++ app/assets/javascripts/views/_shared/chart_totals_view.js.coffee
@@ -115,12 +115,12 @@
     total

 get_gradient_moz = (d) ->
-  return "" if d.date.weekHasPassed
+  return "" if d.date.weekHasPassed || d.date.year == moment().year() and d.date.cweek == (+moment().format('w'))
   percentage = 100 - ((Math.floor(get_proposed_value(d) / get_value(d) * 10000) / 100) || 0)
   "-moz-linear-gradient(to bottom, #5E9B69 " + percentage + "%,  #7EBA8D 0%)"

 get_gradient_webkit = (d) ->
-  return "" if d.date.weekHasPassed
+  return "" if d.date.weekHasPassed || d.date.year == moment().year() and d.date.cweek == (+moment().format('w'))
   percentage = 100 - ((Math.floor(get_proposed_value(d) / get_value(d) * 10000) / 100) || 0)
   "-webkit-linear-gradient(top, #5E9B69 " + percentage + "%,  #7EBA8D 0%)"
 ###*


Fine tune waiting time

Fine tune waiting time

--- spec/integration/helpers/start-atom.coffee
+++ spec/integration/helpers/start-atom.coffee
@@ -114,7 +114,7 @@
       chromedriver.stderr.on "close", ->
         resolve(errorCode)

-  waits(5000)
+  waits(1000)

   waitsFor("webdriver to finish", (done) ->
     finish = once ->
@@ -137,4 +137,4 @@
       finish()

     fn(client.init()).then(finish)
-  , 60000)
+  , 30000)


Corrige bug quando items é null

Corrige bug quando items é null

--- src/coffee/portal-minicart.coffee
+++ src/coffee/portal-minicart.coffee
@@ -93,18 +93,21 @@

 		# Amount Items
 		@cartData.amountItems = 0
-		@cartData.amountItems += item.quantity for item in @cartData.items
+		if @cartData.items
+			@cartData.amountItems += item.quantity for item in @cartData.items

 		# Total
 		total = 0
-		for subtotal in @cartData.totalizers
-			total += subtotal.value if subtotal.id in ['Items', 'Discounts']
+		if @cartData.totalizers
+			for subtotal in @cartData.totalizers
+				total += subtotal.value if subtotal.id in ['Items', 'Discounts']
 		@cartData.totalCart = _.intAsCurrency(total, @options)

 		# Item labels
-		for item in @cartData.items
-			item.availabilityMessage = @getAvailabilityMessage(item)
-			item.formattedPrice = _.intAsCurrency(item.price, @options)
+		if @cartData.items
+			for item in @cartData.items
+				item.availabilityMessage = @getAvailabilityMessage(item)
+				item.formattedPrice = _.intAsCurrency(item.price, @options)

 	render: () =>
 		dust.render 'minicart', @cartData, (err, out) =>


Remove unneccessary comma

Remove unneccessary comma

--- static/src/scripts/common.coffee
+++ static/src/scripts/common.coffee
@@ -2,7 +2,7 @@
   waitSeconds: 15
   baseUrl: '/build/scripts/app'
   paths:
-    'jquery': '../../../components/jquery/dist/jquery',
+    'jquery': '../../../components/jquery/dist/jquery'
     'underscore': '../../../components/underscore/underscore'
     'backbone': '../../../components/backbone/backbone'
     'moment': '../../../components/moment/moment'


Controller#hasSnapshotForLocation

Controller#hasSnapshotForLocation

--- src/turbolinks/controller.coffee
+++ src/turbolinks/controller.coffee
@@ -41,6 +41,9 @@
     console.log "saving snapshot for", @location
     snapshot = @view.saveSnapshot()
     @cache.put(@location, snapshot)
+
+  hasSnapshotForLocation: (location) ->
+    @cache.get(location)?

   restoreSnapshotByScrollingToSavedPosition: (scrollToSavedPosition) ->
     if snapshot = @cache.get(@location)


activity_scores: s/scrs/score_list

activity_scores: s/scrs/score_list

For clarity.

Signed-off-by: Imre Deak <fbd5edba1988036c8923f0cca0cce6ac4811db29@intel.com>

--- static/js/activity_scores.coffee
+++ static/js/activity_scores.coffee
@@ -4,19 +4,19 @@
         @render()

     render: ->
-        scrs = @scores.models
+        score_list = @scores.models
         act_histogram = []

         data_idx = 0
-        while data_idx < scrs.length
-            act = scrs[data_idx].attributes
+        while data_idx < score_list.length
+            act = score_list[data_idx].attributes
             time = act.time
             score = act.score

-            while data_idx + 1 < scrs.length
+            while data_idx + 1 < score_list.length
                 data_idx += 1

-                act = scrs[data_idx].attributes
+                act = score_list[data_idx].attributes

                 if act.time != time
                     break


Hydrator: fix specs.

Hydrator: fix specs.

--- src/OVirtResponseHydrator.coffee
+++ src/OVirtResponseHydrator.coffee
@@ -136,16 +136,14 @@
 #
 # ### Hydrate actions
 #
-# - Detect actions
-# - Instantiates actions objects
-#
-# ### Hydrate properties
+# - Detect action.
+# - Instantiate an actions object an set as a current hydration target.
+# + Proceed with API node hydration procedure.
+#
+# ### Hydrate properties of plain nodes
 #
 # - If node has an attributes then they should be merged to node hash.
-# - For API nodes (e.g. when current target existed) attributes should be
-#     - exportet to current target
-#     - and removed from node hash.
-# - If current target is specified then properties should be exported to it.
+# - For API nodes see "High level objects hydration" section.
 #
 #
 # Utility tasks


Lint CoffeeScript code

Lint CoffeeScript code

Signed-off-by: Valérian Saliou <d691b9c2718f7451d77666d2dda50423f81cd2b8@valeriansaliou.name>

--- src/javascripts/progressio.coffee
+++ src/javascripts/progressio.coffee
@@ -32,8 +32,13 @@

   register: ->
     try
-      __.ProgressioRegistry.register_event 'page:events_state_change', @events_state_change, @
-      __.ProgressioRegistry.register_event 'page:async_load', @events_async_load, @
+      __.ProgressioRegistry.register_event(
+        'page:events_state_change', @events_state_change, @
+      )
+
+      __.ProgressioRegistry.register_event(
+        'page:async_load', @events_async_load, @
+      )
     catch error
       Console.error 'ProgressioPage.register', error

@@ -69,7 +74,8 @@

           try
             # Used by some to open up the target page in a new tab rather
-            if __.ProgressioMisc.key_event().ctrlKey or __.ProgressioMisc.key_event().metaKey
+            if __.ProgressioMisc.key_event().ctrlKey or \
+               __.ProgressioMisc.key_event().metaKey
               return_value = true
             else
               self._run_async_load ($(this).attr 'href')
@@ -82,9 +88,15 @@
         eligible_links.attr 'data-async', 'active'

         if eligible_links_count
-          Console.debug 'ProgressioPage.events_async_load', "Yay! #{eligible_links_count} internal links ajax-ified."
+          Console.debug(
+            'ProgressioPage.events_async_load',
+            "Yay! #{eligible_links_count} internal links ajax-ified."
+          )
       else
-        Console.error 'ProgressioPage.events_async_load', 'Woops, your browser is not compatible with asynchronous page load!'
+        Console.error(
+          'ProgressioPage.events_async_load',
+          'Woops, your browser is not compatible with asynchronous page load!'
+        )
     catch error
       Console.error 'ProgressioPage.events_async_load', error

@@ -131,7 +143,11 @@
       http_base = "#{@_http_protocol}://#{@_http_host}/"
       r_match = new RegExp "^(#{http_base}|(/(?!https?://).*))", 'gi'

-      return @_document_sel.find('a[href]:not([target="_blank"], [data-async="disabled"], [data-async="active"])').filter ->
+      return @_document_sel.find(
+        'a[href]:not([target="_blank"], ' +\
+        '[data-async="disabled"], ' +\
+        '[data-async="active"])'
+      ).filter ->
         return $(this).attr('href').match r_match
     catch error
       Console.error 'ProgressioPage._eligible_links_async_load', error
@@ -182,15 +198,22 @@

           # Callback functions
           callback_counter = 0
-          old_namespaced_script_sel = @_document_sel.find 'script:not([data-scope="common"])'
-          old_namespaced_stylesheet_sel = @_document_sel.find 'link[rel="stylesheet"]:not([data-scope="common"])'
+          old_namespaced_script_sel = @_document_sel.find(
+            'script:not([data-scope="common"])'
+          )
+          old_namespaced_stylesheet_sel = @_document_sel.find(
+            'link[rel="stylesheet"]:not([data-scope="common"])'
+          )

           cb_cleanup_fn = ->
             # Purge old scripts
             old_namespaced_script_sel.remove()
             old_namespaced_stylesheet_sel.remove()

-            Console.debug 'ProgressioPage._handle_async_load', "Done cleanup of old DOM before page: #{url}"
+            Console.debug(
+              'ProgressioPage._handle_async_load',
+              "Done cleanup of old DOM before page: #{url}"
+            )

           cb_post_display_fn = ->
             # Finish load, happily! :)
@@ -203,7 +226,10 @@
               -> __.LayoutComment.reset_last_hash()
             )

-            Console.debug 'ProgressioPage._handle_async_load', "Done post display actions for page: #{url}"
+            Console.debug(
+              'ProgressioPage._handle_async_load',
+              "Done post display actions for page: #{url}"
+            )

           cb_complete_fn = ->
             # Wait a little bit (DOM rendering lag and other joys!)
@@ -218,7 +244,9 @@
                 self._document_sel.find('#body_new').attr('id', 'body').show()

                 # Restore DOM events
-                __.ProgressioRegistry.restore_events '#body'
+                __.ProgressioRegistry.restore_events(
+                  '#body'
+                )

                 # All done, now pushing to the history
                 if url isnt self._state_url
@@ -228,7 +256,9 @@
                 # Trigger post-display events
                 cb_post_display_fn()

-                Console.debug 'ProgressioPage._handle_async_load', "Loaded page: #{url}"
+                Console.debug(
+                  'ProgressioPage._handle_async_load', "Loaded page: #{url}"
+                )
             )

           # Purge old environment
@@ -250,7 +280,9 @@
           )

           @_head.append(
-            data_sel.filter 'link[rel="stylesheet"]:not([href]):not([data-scope="common"])'
+            data_sel.filter(
+              'link[rel="stylesheet"]:not([href]):not([data-scope="common"])'
+            )
           )

           # Items to load
@@ -264,7 +296,9 @@
             if script_src
               load_list.js.push script_src

-          data_sel.filter('link[href][rel="stylesheet"]:not([data-scope="common"])').each ->
+          data_sel.filter(
+            'link[href][rel="stylesheet"]:not([data-scope="common"])'
+          ).each ->
             stylesheet_href = $(this).attr 'href'

             if stylesheet_href
@@ -275,12 +309,17 @@
             if load_list.js.length
               callback_counter++

-              Console.info 'ProgressioPage._handle_async_load', 'Loading scripts...'
+              Console.info(
+                'ProgressioPage._handle_async_load', 'Loading scripts...'
+              )

               LazyLoad.js(
                 load_list.js,
                 ->
-                  Console.info 'ProgressioPage._handle_async_load[async]', 'Scripts fully loaded'
+                  Console.info(
+                    'ProgressioPage._handle_async_load[async]',
+                    'Scripts fully loaded'
+                  )

                   if --callback_counter is 0
                     cb_complete_fn()
@@ -289,27 +328,41 @@
             if load_list.css.length
               callback_counter++

-              Console.info 'ProgressioPage._handle_async_load', 'Loading stylesheets...'
+              Console.info(
+                'ProgressioPage._handle_async_load', 'Loading stylesheets...'
+              )

               LazyLoad.css(
                 load_list.css,
                 ->
-                  Console.info 'ProgressioPage._handle_async_load[async]', 'Stylesheets fully loaded'
+                  Console.info(
+                    'ProgressioPage._handle_async_load[async]',
+                    'Stylesheets fully loaded'
+                  )

                   if --callback_counter is 0
                     cb_complete_fn()
               )

-            Console.debug 'ProgressioPage._handle_async_load', "Delayed page load (waiting for sources to be loaded first): #{url}"
+            Console.debug(
+              'ProgressioPage._handle_async_load',
+              "Delayed page load (waiting for sources to be loaded): #{url}"
+            )
           else
             cb_complete_fn()
         else
           @_show_error_alert(10)
           @_end_progress_bar()

-          Console.error 'ProgressioPage._handle_async_load', "Got an abnormal or error response from: #{url}"
+          Console.error(
+            'ProgressioPage._handle_async_load',
+            "Got an abnormal or error response from: #{url}"
+          )
       else
-        Console.warn 'ProgressioPage._handle_async_load', "Dropped outpaced ID for page: #{url}"
+        Console.warn(
+          'ProgressioPage._handle_async_load',
+          "Dropped outpaced ID for page: #{url}"
+        )
     catch error
       Console.error 'ProgressioPage._handle_async_load', error

@@ -429,9 +482,16 @@
           if not (is_init and cur_registry[2])
             (cur_registry[0].bind cur_registry[1])(parent)

-          Console.debug 'ProgressioRegistry._bind_events[loop]', "Bound callback function for #{ns}"
+          Console.debug(
+            'ProgressioRegistry._bind_events[loop]',
+            "Bound callback function for #{ns}"
+          )
         catch _error
-          Console.error 'ProgressioRegistry._bind_events[loop]', "Registry callback function execution failed for #{ns} with error message: #{_error}"
+          Console.error(
+            'ProgressioRegistry._bind_events[loop]',
+            "Registry callback function execution failed for " + \
+            "#{ns} with error message: #{_error}"
+          )
     catch error
       Console.error 'ProgressioRegistry._bind_events', error

@@ -441,7 +501,9 @@
       ignore_init = false or ignore_init
       @_registry_events[namespace] = [fn_callback, fn_context, ignore_init]

-      Console.info 'ProgressioRegistry.register_event', "Registered event: #{namespace}"
+      Console.info(
+        'ProgressioRegistry.register_event', "Registered event: #{namespace}"
+      )
     catch error
       Console.error 'ProgressioRegistry.register_event', error

@@ -451,7 +513,10 @@
       if namespace in @_registry_events
         delete @_registry_events[namespace]

-        Console.info 'ProgressioRegistry.unregister_event', "Unregistered event: #{namespace}"
+        Console.info(
+          'ProgressioRegistry.unregister_event',
+          "Unregistered event: #{namespace}"
+        )

         return true

@@ -491,7 +556,9 @@
           delete _[cur_bundle]
           count_unload++

-      Console.info 'ProgressioRegistry.unload_bundles', "Unloaded #{count_unload} bundles"
+      Console.info(
+        'ProgressioRegistry.unload_bundles', "Unloaded #{count_unload} bundles"
+      )
     catch error
       Console.error 'ProgressioRegistry.unload_bundles', error


finder controller: make "reset" always asynchronous

--- client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -71,7 +71,7 @@
     if @getOptions().useStorage
       @appStorage.once "storageFetched", @bound 'loadVms'
     else
-      @loadVms()
+      @utils.defer => @loadVms()

   mountVms: (vms) ->
     unless Array.isArray vms


Update _invitation_form.js.coffee

Update _invitation_form.js.coffee

--- app/assets/javascripts/app/custom_bigbluebutton_rooms/_invitation_form.js.coffee
+++ app/assets/javascripts/app/custom_bigbluebutton_rooms/_invitation_form.js.coffee
@@ -61,7 +61,7 @@
         results: (data, page) -> # parse the results into the format expected by Select2.
           results: data

-    # This option will open the select2 field to be ready to search
+    # This option will open the select2 field ready to search
     $(usersSelector).select2 'open'

     # This option is optional. It was used just to improve the style of the page


Graph: Added group slugs into secret groups

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -133,7 +133,11 @@
       ]
       slug:
         $nin: ["koding"] # we need koding even if its private
-    , {}, callback
+    , {}, (err, groups)=>
+      return callback err if err
+      return callback null, [] if groups.length < 1
+      secretGroups =  (group.slug for group in groups)
+      callback null, secretGroups

   # we may need to add public group's read permission checking
   removePrivateContent:(client, groupId, contents, callback)->


groups: also persist limits

groups: also persist limits

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1175,16 +1175,22 @@
         -> callback null
       ]

-  sendNotificationToAdmins: (event, contents)->
-    @fetchAdmins (err, admins)=>
+  sendNotificationToAdmins: (event, contents) ->
+    @fetchAdmins (err, admins) =>
       unless err
         for admin in admins
           admin.sendNotification event, contents

-  updateBundle: (formData, callback = (->))->
-    @fetchBundle (err, bundle)->
+  updateBundle: (formData, callback = (->)) ->
+    @fetchBundle (err, bundle) =>
       return callback err  if err?
       bundle.update $set: { overagePolicy: formData.overagePolicy }, callback
+      bundle.fetchLimits (err, limits) ->
+        return callback err  if err?
+        queue = limits.map (limit) -> ->
+          limit.update { $set: quota: formData.quotas[limit.title] }, fin
+        dash queue, callback
+        fin = queue.fin.bind queue

   updateBundle$: permit 'change bundle',
     success: (client, formData, callback)->


social/groups: change permission to get members for `members`

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -756,8 +756,8 @@

   fetchUserRoles: permit
     advanced: [
-      { permission: 'grant permissions' }
-      { permission: 'grant permissions', superadmin: yes }
+      { permission: 'list members' }
+      { permission: 'list members', superadmin: yes }
     ]
     success:(client, ids, callback) ->
       [callback, ids] = [ids, callback]  unless callback
@@ -801,8 +801,8 @@

   fetchMembersWithEmail$: permit
     advanced: [
-      { permission: 'grant permissions' }
-      { permission: 'grant permissions', superadmin: yes }
+      { permission: 'list members' }
+      { permission: 'list members', superadmin: yes }
     ]
     success:(client, rest...) ->
       @baseFetcherOfGroupStaff {


Mesh import fix.

Mesh import fix.

--- packages/landsofillusions-assets/spriteeditor/spriteeditor.coffee
+++ packages/landsofillusions-assets/spriteeditor/spriteeditor.coffee
@@ -8,12 +8,6 @@
 class LOI.Assets.SpriteEditor extends LOI.Assets.Editor
   @id: -> 'LandsOfIllusions.Assets.SpriteEditor'
   @register @id()
-
-  constructor: ->
-    super arguments...
-
-    @documentClass = LOI.Assets.Sprite
-    @assetClassName = @documentClass.className

   @defaultInterfaceData: ->
     # Operators
@@ -201,6 +195,12 @@
       "#{LOI.Assets.SpriteEditor.Tools.Pencil.id()}": key: AC.Keys.b
       "#{LOI.Assets.SpriteEditor.Tools.Translate.id()}": key: AC.Keys.v

+  constructor: ->
+    super arguments...
+
+    @documentClass = LOI.Assets.Sprite
+    @assetClassName = @documentClass.className
+
   onRendered: ->
     super arguments...


IPython notebooks were loading with the iframe size set incorrectly: fix this and more generally make it so tabs never display if not active.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -946,6 +946,9 @@
     constructor: (@editor, @filename, content, opts) ->
         @val(content)

+    is_active: () =>
+        return @editor._active_tab_filename == @filename
+
     init_autosave: () =>
         if @_autosave_interval?
             # This function can safely be called again to *adjust* the
@@ -1009,6 +1012,8 @@
         return local_storage(@editor.project_id, @filename, key, value)

     show: (opts={}) =>
+        if not @is_active()
+            return
         @element.show()

     hide: () =>
@@ -1218,6 +1223,9 @@
             @_split_view = false

         @init_change_event()
+
+    is_active: () =>
+        return @codemirror? and @editor? and @editor._active_tab_filename == @filename

     set_theme: (theme) =>
         # Change the editor theme after the editor has been created
@@ -1518,7 +1526,7 @@
                         cm.scrollIntoView(pos, info.clientHeight/2)
                     catch e
                         #console.log("#{@filename}: failed to scroll view #{cm.name} into view -- #{e}")
-
+

     # set background color of active line in editor based on background color (which depends on the theme)
     _style_active_line: () =>
@@ -1536,8 +1544,7 @@
         $("body").append("<style id='salvus-cm-activeline' type=text/css>.CodeMirror-activeline{background:rgb(#{v[0]},#{v[1]},#{v[2]});}</style>")

     show: () =>
-
-        if not (@element? and @codemirror?) or @editor._active_tab_filename != @filename
+        if not @is_active()
             return

         # Show gets called repeatedly as we resize the window, so we wait until slightly *after*
@@ -2449,6 +2456,8 @@
             top    : undefined
             width  : $(window).width()
             height : undefined
+        if not @is_active()
+            return

         @element.show()

@@ -2706,6 +2715,8 @@
             @worksheet.process_sage_updates()

     show: () =>
+        if not @is_active()
+            return
         @element?.show()
         @history_editor?.show()
         if @ext == 'sagews'
@@ -3021,6 +3032,9 @@
         @latex_editor._set(content)

     show: () =>
+        if not @is_active()
+            return
+
         @element?.show()
         @latex_editor?.show()
         if not @_show_before?
@@ -3391,6 +3405,8 @@
         @element.remove()

     show: () =>
+        if not @is_active()
+            return
         @element.show()
         if @console?
             e = $(@console.terminal.element)
@@ -3515,6 +3531,8 @@
             @worksheet?.focus()

     show: () =>
+        if not @is_active()
+            return
         if not @worksheet?
             return
         @element.show()
@@ -3606,6 +3624,8 @@
                     cb?()

     show: () =>
+        if not @is_active()
+            return
         @element.show()
         @element.css(top:@editor.editor_top_position())
         @element.maxheight()
@@ -3801,8 +3821,9 @@
             (cb) =>
                 if @readonly
                     # TODO -- change UI to say *READONLY*
-                    @iframe.animate(opacity:1)
+                    @iframe.css(opacity:1)
                     @save_button.text('Readonly').addClass('disabled')
+                    @show()
                     cb()
                 else
                     @_init_doc(cb)
@@ -3825,7 +3846,8 @@
             # already initialized
             @doc.sync () =>
                 @set_live_from_syncdoc()
-                @iframe.animate(opacity:1)
+                @iframe.css(opacity:1)
+                @show()
                 cb?()
             return
         @doc = syncdoc.synchronized_string
@@ -3852,7 +3874,8 @@
         else
             @set_live_from_syncdoc()
         #console.log("DONE SETTING!")
-        @iframe.animate(opacity:1)
+        @iframe.css(opacity:1)
+        @show()

         @doc._presync = () =>
             if not @nb? or @_reloading
@@ -4410,15 +4433,17 @@
         # console.log("ipython notebook focus: todo")

     show: () =>
+        if not @is_active()
+            return
         @element.show()
         top = @editor.editor_top_position()
         @element.css(top:top)
         if top == 0
             @element.css('position':'fixed')
         w = $(window).width()
+        # console.log("top=#{top}; setting maxheight for iframe =", @iframe)
         @iframe?.attr('width',w).maxheight()
-
-
+        setTimeout((()=>@iframe?.maxheight()), 1)   # set it one time more the next render loop.

 class FileEditorWrapper extends FileEditor
     constructor: (@editor, @filename) ->
@@ -4451,6 +4476,8 @@
         @wrapped.destroy?()

     show: () =>
+        if not @is_active()
+            return
         @element.show()
         if not IS_MOBILE
             @element.css(top:@editor.editor_top_position(), position:'fixed')


Early return only applies to fault tolerant request

We need to return the data to other requests like getLastOccurrence.

--- services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee
+++ services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee
@@ -17,17 +17,17 @@
 	if settings.overleaf?
 		options.qs = Object.assign({}, options.qs, { fromV2: 1 })

-	makeRequest(options, callback)
+	makeRequest options, (err) ->
+		if err?
+			logger.err { err: err }, 'Request to analytics failed'
+
+	callback() # Do not wait for all the attempts

 makeRequest = (opts, callback)->
 	if settings.apis?.analytics?.url?
 		urlPath = opts.url
 		opts.url = "#{settings.apis.analytics.url}#{urlPath}"
-		request opts, (err) ->
-			if err?
-				logger.err { err: err }, 'Request to analytics failed'
-
-		callback() # Do not wait for all the attempts
+		request opts, callback
 	else
 		callback(new Errors.ServiceNotConfiguredError('Analytics service not configured'))


Workspace nodes show the path instead of "Untitled"

--- scripts/gh-book/xhtml-file.coffee
+++ scripts/gh-book/xhtml-file.coffee
@@ -15,6 +15,9 @@
   # * `authors` - an `Collection` of `User`s that are attributed as authors
   return class XhtmlModel extends ModuleModel
     mediaType: 'application/xhtml+xml'
+
+    defaults:
+      title: null

     initialize: () ->
       # Clear that the title on the model has changed


SocialWorker: Tests: pass next to each function

--- workers/social/testhelper/models/computeproviders/computeproviderhelper.coffee
+++ workers/social/testhelper/models/computeproviders/computeproviderhelper.coffee
@@ -22,8 +22,7 @@

   for providerSlug, provider of PROVIDERS
     queue.push (next) ->
-      fn providerSlug, provider
-      next()
+      fn providerSlug, provider, next

   async.series queue, callback


Use ELECTRON_RUN_AS_NODE Variable Key

Use ELECTRON_RUN_AS_NODE Variable Key

- https://github.com/atom/electron/pull/3594

--- src/buffered-node-process.coffee
+++ src/buffered-node-process.coffee
@@ -46,7 +46,7 @@

     options ?= {}
     options.env ?= Object.create(process.env)
-    options.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = 1
+    options.env['ELECTRON_RUN_AS_NODE'] = 1

     args = args?.slice() ? []
     args.unshift(command)


Fixed case of '*3'

Fixed case of '*3'

--- server/src/EquationsParser.coffee
+++ server/src/EquationsParser.coffee
@@ -1,3 +1,16 @@
+###
+Tests:
+
+-(5+5)
+---+++5
++++---5
+1++2
+1--2
+-1--3
+2*3
+*3
+**3
+###
 class Node
   type: undefined     # 'number'/'binop'/'unaryop'
   token: undefined    # The token
@@ -99,7 +112,7 @@
     if c == '('
       ++@idx
       node = @handleAddMinus()
-      if @expr[@idx] != ')' then throw "Error Unbalanced Parenthesis"
+      if @expr[@idx] != ')' then throw new Error("Error Unbalanced Parenthesis")
       ++@idx # move past the '('
     else
       node = @atom()
@@ -114,6 +127,8 @@
     else if this.isIdent(c)
       node.type = "ident"
       node.text = this.matchIdent()
+    else
+      throw new Error("UNEXPECTED TOKEN: #{c}")

     node

@@ -132,7 +147,7 @@

 p = new MathParser()
-prettyPrint p.parse("(1+(2*6)-6/3*(5+5*9/2))/2+3")
+prettyPrint p.parse "**3"

 # Export these so I can test them
 exports.MathParser = MathParser


Add US CDC data

Add US CDC data

--- server/syncStructuredFeeds.coffee
+++ server/syncStructuredFeeds.coffee
@@ -5,17 +5,15 @@
 import Constants from '/imports/constants.coffee'

 module.exports = ->
-  countryToGeoname = {}
-  getCountryGeonames = (facts) ->
-    facts.forEach (fact) ->
-      country = fact.dim.COUNTRY
-      console.log country
-      if not countryToGeoname[country]
+  nameToGeoname = {}
+  getGeonames = (countryNames) ->
+    countryNames.forEach (country) ->
+      if not nameToGeoname[country]
         geonamesResult = HTTP.get Constants.GRITS_URL + '/api/geoname_lookup/api/lookup',
           params:
             q: country
-        countryToGeoname[country] = geonamesResult.data.hits[0]._source
-        delete countryToGeoname[country].alternateNames
+        nameToGeoname[country] = geonamesResult.data.hits[0]._source
+        delete nameToGeoname[country].alternateNames

   # Import Cholera data
   WHOCholeraDataUrl = "http://apps.who.int/gho/athena/data/GHO/CHOLERA_0000000001.json?profile=simple&filter=COUNTRY:*;REGION:*"
@@ -31,28 +29,26 @@
         structuredData: true
     })
     feedId = Feeds.findOne(url: WHOCholeraDataUrl)._id
-    HTTP.get WHOCholeraDataUrl, {}, (err, resp)->
-      if err
-        return console.log(err)
-      getCountryGeonames(resp.data.fact)
-      Incidents.remove(sourceFeed: feedId)
-      resp.data.fact.forEach (fact) ->
-        Incidents.insert
-          sourceFeed: feedId
-          constraining: true
-          dateRange:
-            type: "precise"
-            start: new Date(fact.dim.YEAR + "")
-            end: new Date(parseInt(fact.dim.YEAR) + 1 + "")
-          locations: [countryToGeoname[fact.dim.COUNTRY]]
-          cases: parseInt(fact.Value)
-          resolvedDisease:
-            id: "http://purl.obolibrary.org/obo/DOID_1498"
-            text: "Cholera"
-          species:
-            id: "tsn:180092"
-            text: "Homo sapiens"
-          addedDate: new Date()
+    resp = HTTP.get(WHOCholeraDataUrl, {})
+    getGeonames(resp.data.fact.map (fact)-> fact.dim.COUNTRY)
+    Incidents.remove(sourceFeed: feedId)
+    resp.data.fact.forEach (fact) ->
+      Incidents.insert
+        sourceFeed: feedId
+        constraining: true
+        dateRange:
+          type: "precise"
+          start: new Date(fact.dim.YEAR + "")
+          end: new Date(parseInt(fact.dim.YEAR) + 1 + "")
+        locations: [nameToGeoname[fact.dim.COUNTRY]]
+        cases: parseInt(fact.Value)
+        resolvedDisease:
+          id: "http://purl.obolibrary.org/obo/DOID_1498"
+          text: "Cholera"
+        species:
+          id: "tsn:180092"
+          text: "Homo sapiens"
+        addedDate: new Date()

   # Import TB data
   WHOTBDataUrl = "http://apps.who.int/gho/athena/data/GHO/MDG_0000000020,TB_e_inc_num,TB_e_inc_tbhiv_100k,TB_e_inc_tbhiv_num.json?profile=simple&filter=COUNTRY:*;REGION:*"
@@ -67,30 +63,112 @@
         structuredData: true
     })
     feedId = Feeds.findOne(url: WHOTBDataUrl)._id
-    HTTP.get WHOTBDataUrl, {}, (err, resp)->
-      if err
-        return console.log(err)
-      getCountryGeonames(resp.data.fact)
+    resp = HTTP.get(WHOTBDataUrl, {})
+    getGeonames(resp.data.fact.map (fact)-> fact.dim.COUNTRY)
+    Incidents.remove(sourceFeed: feedId)
+    resp.data.fact.forEach (fact) ->
+      if fact.dim.GHO == "Number of incident tuberculosis cases"
+        parsedValue = /(\d+) \[(\d+)\-(\d+)\]/.exec(fact.Value)
+        maxValue = parsedValue[3]
+        Incidents.insert
+          sourceFeed: feedId
+          constraining: true
+          dateRange:
+            type: "precise"
+            start: new Date(fact.dim.YEAR + "")
+            end: new Date(parseInt(fact.dim.YEAR) + 1 + "")
+          locations: [nameToGeoname[fact.dim.COUNTRY]]
+          cases: parseInt(maxValue)
+          resolvedDisease:
+            id: "http://purl.obolibrary.org/obo/DOID_399"
+            text: "Tuberculosis"
+          species:
+            id: "tsn:180092"
+            text: "Homo sapiens"
+          addedDate: new Date()
+
+  # Import CDC data
+  CDCDataURLs = [
+    url: "https://data.cdc.gov/resource/w3an-exa3.json"
+    title: "NNDSS Meningococcal disease to Pertussis"
+    diseases: [
+      id: "http://purl.obolibrary.org/obo/DOID_0080176"
+      text: "Meningococcal Meningitis"
+      valProp: "meningococcal_disease_all_serogroups_current_week"
+    ,
+      id: "http://purl.obolibrary.org/obo/DOID_1116"
+      text: "pertussis"
+      valProp: "pertussis_current_week"
+    ,
+      id: "http://purl.obolibrary.org/obo/DOID_10264"
+      text: "Mumps"
+      valProp: "mumps_current_week"
+    ]
+  ,
+    url: "https://data.cdc.gov/resource/yi9b-zyiu.json"
+    title: "NNDSS Legionellosis to Malaria"
+    diseases: [
+      id: "http://purl.obolibrary.org/obo/DOID_10458"
+      text: "Legionellosis"
+      valProp: "legionellosis_current_week"
+    ,
+      id: "http://purl.obolibrary.org/obo/DOID_12365"
+      text: "Malaria"
+      valProp: "malaria_current_week"
+    ]
+  ,
+    url: "https://data.cdc.gov/resource/f5br-wfa6.json"
+    title: "NNDSS Invasive pneumococcal diseases (all ages)"
+    diseases: [
+      id: "http://purl.obolibrary.org/obo/DOID_552"
+      text: "Pneumonia"
+      valProp: "invasive_pneumococcal_disease_all_ages_confirmed_current_week"
+    ]
+  ]
+  CDCDataURLs.forEach (CDCItem)->
+    feed = Feeds.findOne(url: CDCItem.url)
+    if true or not (feed and moment().isBefore(moment(feed.addedDate).add(7, 'days')))
+      Feeds.upsert({
+        url: CDCItem.url
+      }, {
+        $set:
+          title: CDCItem.title
+          addedDate: new Date()
+          structuredData: true
+      })
+      feedId = Feeds.findOne(url: CDCItem.url)._id
+      resp = HTTP.get(CDCItem.url, {})
+      getGeonames(resp.data.map (d)-> d.reporting_area)
       Incidents.remove(sourceFeed: feedId)
-      resp.data.fact.forEach (fact) ->
-        if fact.dim.GHO == "Number of incident tuberculosis cases"
-          parsedValue = /(\d+) \[(\d+)\-(\d+)\]/.exec(fact.Value)
-          maxValue = parsedValue[3]
-          Incidents.insert
-            sourceFeed: feedId
-            constraining: true
-            dateRange:
-              type: "precise"
-              start: new Date(fact.dim.YEAR + "")
-              end: new Date(parseInt(fact.dim.YEAR) + 1 + "")
-            locations: [countryToGeoname[fact.dim.COUNTRY]]
-            cases: parseInt(maxValue)
-            resolvedDisease:
-              id: "http://purl.obolibrary.org/obo/DOID_399"
-              text: "Tuberculosis"
-            species:
-              id: "tsn:180092"
-              text: "Homo sapiens"
-            addedDate: new Date()
+      resp.data.forEach (dataItem)->
+        {
+          mmwr_week,
+          mmwr_year,
+          reporting_area
+        } = dataItem
+        mmwr_week = parseInt(mmwr_week)
+        mmwr_year = parseInt(mmwr_year)
+        startDate = moment("#{mmwr_year}-01-01").add((parseInt(mmwr_week) - 1) * 7, 'days').toDate()
+        endDate = moment(startDate).add(7, 'days').toDate()
+        CDCItem.diseases.forEach (disease)->
+          value = parseInt(dataItem[disease.valProp])
+          location = nameToGeoname[reporting_area]
+          if value.toString() != "NaN" and location.featureCode == "ADM1" and location.countryCode == "US"
+            Incidents.insert
+              sourceFeed: feedId
+              constraining: true
+              dateRange:
+                type: "precise"
+                start: startDate
+                end: endDate
+              locations: [location]
+              cases: value
+              resolvedDisease:
+                id: disease.id
+                text: disease.text
+              species:
+                id: "tsn:180092"
+                text: "Homo sapiens"
+              addedDate: new Date()

-    console.log "Constraining incidents updated"
+  console.log "Constraining incidents updated"


server: custom logic implementation for serving home page

--- servers/lib/server/helpers.coffee
+++ servers/lib/server/helpers.coffee
@@ -106,6 +106,20 @@
   res.header 'Content-type', 'text/html'
   res.send content

+isTeamPage = (req) ->
+  hostname = req?.headers?['x-host']
+  return no  unless hostname
+  for i, env of ['dev', 'sandbox', 'latest']
+    if hostname.indexOf(env) is 0 # damn nodejs doesnt have startsWith
+      return no
+
+  subdomains = hostname.split('.')
+
+  if subdomains.length < 3 # only koding.com has 2
+    return no
+
+  return yes
+
 serveHome = (req, res, next) ->
   { JGroup } = bongoModels = koding.models
   { generateFakeClient }   = require './client'
@@ -114,30 +128,45 @@
     if err or not client
       console.error err
       return next()
-    isLoggedIn req, res, (err, state, account) ->
+    isLoggedIn req, res, (err, isLoggedIn, account) ->
       if err
         res.status(500).send error_500()
         return console.error err

-      # if user is not logged in and requests the root page, serve the content
-      # of hubspot
-      if not state and req.path is '/' and KONFIG.environment not in ['dev', 'sandbox']
-
-        # pipe incoming request directly to hubspot and pipe the response from
-        # hubspot as our response
-        return req.pipe(request(KONFIG.hubspotPageURL)).pipe(res)
-
-      else
-        client.connection.delegate = account
-
-        { params }              = req
-        { loggedIn, loggedOut } = JGroup.render
-        fn                      = if state then loggedIn else loggedOut
-        options = { client, account, bongoModels, params, session }
-
+      # construct options
+      client.connection.delegate = account
+      { params }                 = req
+      { loggedIn, loggedOut }    = JGroup.render
+      fn                         = if isLoggedIn then loggedIn else loggedOut
+      options                    = { client, account, bongoModels, params, session }
+
+      serveKodingHome = ->
         fn.kodingHome options, (err, subPage) ->
           return next()  if err
-          serve subPage, res
+          return serve subPage, res
+
+      if req.path isnt '/'
+        return serveKodingHome()
+
+      # main path has a special case where all users should be redirected to
+      # hubspot
+
+      # if incoming request goes to a team page, should resolve immediately -
+      # without a redirection requirement
+      if isTeamPage(req)
+        return serveKodingHome() if isLoggedIn
+
+        return res.redirect 307, '/Login'
+
+      # but if we are in dev or sandbox env, serve content as we used to
+      if KONFIG.environment in ['dev', 'sandbox']
+        return serveKodingHome()  if isLoggedIn
+
+        return req.pipe(request(KONFIG.hubspotPageURL)).pipe(res)
+
+      # all other requests coming to slash, goes back to KONFIG.hubspotPageURL
+      return res.redirect 307, KONFIG.hubspotPageURL
+

 isLoggedIn = (req, res, callback) ->
@@ -313,4 +342,5 @@
   isMainDomain
   setSessionCookie
   checkAuthorizationBearerHeader
+  isTeamPage
 }


added /oops endpoint for generating dummy exception

--- services/track-changes/app.coffee
+++ services/track-changes/app.coffee
@@ -50,6 +50,9 @@
 app.get "/status", (req, res, next) ->
 	res.send "track-changes is alive"

+app.get "/oops", (req, res, next) ->
+	throw new Error("dummy test error")
+
 app.get "/check_lock", HttpController.checkLock

 app.get "/health_check",  HttpController.healthCheck


adds order for related records

adds order for related records

--- lib/Model.coffee
+++ lib/Model.coffee
@@ -57,7 +57,12 @@

           # add getter
           @::["get" + capName] = (cb = ->) ->
-            RelModel.r().getAll(@getId(), index: opts.foreignKey).run db.conn, RelModel.wrapReply cb
+            query = new Query RelModel, RelModel.r().getAll(@getId(), index: opts.foreignKey)
+            query.collection = yes
+            query.order opts.order if opts.order
+            if cb?
+              query.run cb
+            else query

           @::["set" + capName] = (models, cb = ->) ->
             models = wrapModel models, RelModel
@@ -83,14 +88,18 @@
         RelModel = db.models[opts.model]

         @::["get" + capName] = (cb = ->) ->
-          RelModel.r().get(@[opts.foreignKey]).run db.conn, RelModel.wrapReply cb
+          query = new Query RelModel, RelModel.r().get(@[opts.foreignKey])
+          query.collection = no
+          if cb?
+            query.run cb
+          else query

         @["getBy" + capName] = (id, cb = ->) =>
-            query = new Query @, @r().getAll(id, {index:opts.foreignKey})
-            query.collection = yes
-            if cb?
-              query.run cb
-            else query
+          query = new Query @, @r().getAll(id, {index:opts.foreignKey})
+          query.collection = yes
+          if cb?
+            query.run cb
+          else query

     @r: ->
       r.db(db.config.db).table @tableName


Add TimelineCtrl controller.

Add TimelineCtrl controller.

--- app/javascripts/controllers.coffee
+++ app/javascripts/controllers.coffee
@@ -3,7 +3,7 @@
   (ng, _) ->
     ctlrs = ng.module(
       'qiprofile.controllers',
-      ['ngSanitize', 'ui.bootstrap', 'nvd3', 'qiprofile.resources', 'qiprofile.modelingchart',
+      ['ngSanitize', 'nvd3', 'qiprofile.resources', 'qiprofile.modelingchart',
        'qiprofile.breast', 'qiprofile.slicedisplay', 'qiprofile.correlation']
     )

@@ -325,6 +325,50 @@
         else
           throw new Error("The modeling source has neither a scan" +
                           " nor a registration protocol reference")
+    ]
+
+
+    ctlrs.controller 'TimelineCtrl', [
+      '$scope', '$state', '$compile', 'Timeline',
+      ($scope, $state, $compile, Timeline) ->
+        # The chart {options, data} configuration.
+        chartConfig = Timeline.configure($scope.subject)
+        $scope.options = chartConfig.options
+        $scope.data = chartConfig.data
+        # The global configuration. Disable the data watcher since,
+        # once built, the data is not changed. Furthermore, the
+        # data is the modeling parameter objects, which are complex
+        # objects with a circular object graph reference
+        # (paramResult -> parent modelingResult -> child paramResult).
+        $scope.config = deepWatchData: false
+        # The chart decoration callback.
+        $scope.decorate = (scope, element) ->
+          # Note: since the nvd3 directive isolates the scope,
+          # it does not inherit to the parent $scope properties.
+          # Therefore, we must specify $scope.subject rather than
+          # scope.subject.
+          Timeline.decorate(element, $scope.subject, scope, $state)
+
+          # Note: an angular-nvd3 bug voids element changes made
+          # in this callback on refresh
+          # (cf. https://github.com/krispo/angular-nvd3/issues/316).
+          # The work-around is to surgically intervene in the
+          # defective ngnvd3 code.
+          if _.isUndefined(scope.isBroken)
+            broken = scope.api.refresh
+            fixed = ->
+              # Note: setting scope.isReady should trigger the watcher
+              # but doesn't. The work-around to this work-around bug is
+              # to force the issue by calling the decorate function
+              # directly in the refresh rather than indirectly via the
+              # watcher.
+              # TODO - periodically check whether the ngnvd3 bug is
+              #   fixed and remove the work-around if possible.
+              #scope.isReady = false
+              broken()
+              $scope.decorate(scope, element)
+            scope.api.refresh = fixed
+            scope.isBroken = false
     ]


Substitution toggles original document

Substitution toggles original document

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -2,23 +2,45 @@
 $ = Annotator.$
 xpathRange = Annotator.Range
 Util = Annotator.Util
+original_document = ''

 # This plugin implements the UI code for selecting sentences by clicking
 module.exports = class Substitution extends Annotator.Plugin

   pluginInit: ->
-# Register the event handlers required for creating a selection
+  # Register the event handlers required for creating a selection
+
     $(document).bind({
-      "click": @makeSubstitution
+      "click": @startSubstitution
     })
+
+    this.original_document = ""

     null

   destroy: ->
     $(document).unbind({
-      "click": @makeSubstitution
+      "click": @startSubstitution
     })
     super
+
+
+  startSubstitution: (event = {}) =>
+    if this.original_document == ""
+      alert("storing")
+      this.original_document = `$("body").html()`
+
+    if this.original_document != `$("body").html()`
+      `
+      alert(this.original_document);
+      $("body").html(this.original_document);
+      `
+      return null
+    else
+      alert("running sub")
+      this.makeSubstitution(event)
+
+

 # This is called when the mouse is clicked on a DOM element.
 # Checks to see if there is a sentence that we can select, if so
@@ -28,8 +50,6 @@
 #
 # Returns nothing.
   makeSubstitution: (event = {}) =>
-
-    #alert(Util.xpathFromNode($(event.target), document))

     start_xpath = "/html[1]/body[1]/div[2]/h2[1]"
     end_xpath = "/html[1]/body[1]/div[2]/div[5]/p[2]"
@@ -133,3 +153,5 @@
             return false

       return true
+
+


Hide adder when moving to edit mode

Hide adder when moving to edit mode

Fixes #67

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -44,6 +44,8 @@
           if $(ele).hasClass("annotator-notice") or $(ele).hasClass("annotator-frame") or $(ele).hasClass("annotator-adder")
           else if ele.nodeName != "SCRIPT"
             $(annotatorComponents[0]).before($(ele).clone())
+
+      this.annotator.adder.hide()

       return null


only show sequences as behavior

only show sequences as behavior

--- src/layout/mixins/programs.coffee
+++ src/layout/mixins/programs.coffee
@@ -96,10 +96,12 @@
     ]
     my.extend program, program_behavior()

-  behavior = sprite.get('behavior')
-  my.assert _.isObject(behavior), "#{sprite} has no behavior property"
+  actions = sprite.get 'actions'
+  my.assert sprite.is_world(actions), "#{sprite} has no actions world"
+  my.assert actions.keys(), "#{sprite} has action keys"

   children = []
-  for name, contents of behavior
-    children.push program_row(name, contents)
-  children
+  actions.keys().map (key) ->
+    contents = actions.get(key)
+    children.push program_row(key, contents) unless _.isString(contents)
+  children


remove console log

remove console log

--- app/assets/javascripts/trade/controllers/sandbox_shipments_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/sandbox_shipments_controller.js.coffee
@@ -42,7 +42,6 @@
   transitionToParentController: ->
     model = @get('controllers.annualReportUpload.content')
     model.reload()
-    console.log(model)
     @customTransitionToRoute(
       'annual_report_upload',
       model,


Added GAMA group and workflow ids

Added GAMA group and workflow ids


Rotate storms

Rotate storms


Generalise Geocoder service to Geo, add distanceBetween method

--- app/assets/javascripts/darkswarm/services/geocoder.js.coffee
+++ app/assets/javascripts/darkswarm/services/geocoder.js.coffee
@@ -1,14 +1,18 @@
 # Usage:
-# Geocoder.geocode address, (results, status) ->
-#   if status == Geocoder.OK
+# Geo.geocode address, (results, status) ->
+#   if status == Geo.OK
 #     console.log results[0].geometry.location
 #   else
 #     console.log "Error: #{status}"

-Darkswarm.service "Geocoder", ->
-  new class Geocoder
+Darkswarm.service "Geo", ->
+  new class Geo
     OK: google.maps.GeocoderStatus.OK

     geocode: (address, callback) ->
       geocoder = new google.maps.Geocoder()
       geocoder.geocode {'address': address}, callback
+
+    distanceBetween: (locatable, location) ->
+      latLng = new google.maps.LatLng locatable.latitude, locatable.longitude
+      google.maps.geometry.spherical.computeDistanceBetween latLng, results[0].geometry.location


Tidy up the output.

Tidy up the output.

--- test/conf/protractor-conf.coffee
+++ test/conf/protractor-conf.coffee
@@ -11,3 +11,7 @@
   specs: ['../e2e/**/*Spec.coffee']

   onPrepare: '../e2e/helpers/seed'
+
+  mochaOpts:
+    reporter: 'spec'
+    slow: 1200


Small typo fix

Small typo fix

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- app/models/ban.coffee
+++ app/models/ban.coffee
@@ -26,6 +26,6 @@

 schema.virtual('permanent').get -> not @temporary

-schema.virtual('temporary').get -> @date isnt null
+schema.virtual('temporary').get -> @end isnt null

 module.exports = mongoose.model 'Ban', schema


Add an actually-useful error message when to hooks

--- lib/hooks.coffee
+++ lib/hooks.coffee
@@ -4,5 +4,21 @@

 module.exports =
   trigger: (address, args...) ->
-    if handler = readsConfiguration.read("hooks.#{address}")
-      handler(address, args...)
+    hook = "hooks.#{address}"
+    if handler = readsConfiguration.read(hook)
+      try
+        handler(address, args...)
+      catch e
+        console.warn """
+                     Encountered an exception while invoking hook '#{hook}':
+
+                     Error:
+
+                     #{e.message}
+
+                     Hook source:
+
+                     #{handler.toString()}
+
+                     """
+        throw e