Simplify syntax

Simplify syntax

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -39,8 +39,7 @@
     else
       data['activityKey'] = activityKey

-    weeklyActivityDeferred = getPublicWeeklyActivity(res, data.activityKey)
-    weeklyActivityDeferred.then (activityDetails) ->
+    getPublicWeeklyActivity(res, data.activityKey).then (activityDetails) ->

       payload =
         message: res.message
@@ -49,6 +48,7 @@
       robot.emit('slack-attachment', payload)

     ,(err) ->
+
       sendError(robot, res, err)

     # # interprets input based on length
@@ -277,13 +277,13 @@
     if activityKey not in constants.FURTHER_DETAILS
       return deferred.resolve(activityDetails)

-    parseActivityDeferred = parseActivityHash(bot, activityDetails.activityHash)
-
-    parseActivityDeferred.then (details) ->
+    parseActivityHash(bot, activityDetails.activityHash).then (details) ->
+
       combinedDetails = Object.assign {}, activityDetails, details
       deferred.resolve(combinedDetails)

     ,(err) ->
+
       deferred.reject(err)

   deferred.promise


you can be logged out and edit a team still

--- controllers/middleware.coffee
+++ controllers/middleware.coffee
@@ -9,6 +9,7 @@
   ensureAuth: ensureAuth

   ensureAccess: (req, res, next) ->
+    return next() if req.team? and req.team.includes(null, req.session.team)
     ensureAuth req, res, ->
       unless req.user.admin
         return next 401 if req.person? and req.person.id isnt req.user.id


ErrorReporter prints a message #220

ErrorReporter prints a message #220

--- wzk/debug/ErrorReporter.coffee
+++ wzk/debug/ErrorReporter.coffee
@@ -16,4 +16,4 @@
     @param {Object} e an exception
   ###
   report: (e) ->
-    @log goog.testing.stacktrace.canonicalize e.stack
+    @log e.message, goog.testing.stacktrace.canonicalize e.stack


Enable the personal points command.

Enable the personal points command.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -92,11 +92,12 @@

         msg.send "Only mods can check the points of others!"

-    # robot.respond /points$/i, (msg) ->
-    #     username = "#{msg.envelope.user.name}"
-    #     points[username] ?= 0
-    #
-    #     msg.send "#{msg.envelope.user.name}, you have " + points[username] + " points!"
+    # Allows viewers to check their own point count
+    robot.respond /points$/i, (msg) ->
+        username = "#{msg.envelope.user.name}"
+        points[username] ?= 0
+
+        msg.send "#{msg.envelope.user.name}, you have " + points[username] + " points!"

     setInterval (->
         robot.http("https://tmi.twitch.tv/group/user/masonest/chatters").get() (err, res, body) ->


senpai plusplus

senpai plusplus

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -6,19 +6,26 @@
 #   hubot 誰知ってるんですか? - hubot が知ってる人を教えてくれるよ！
 #   hubot 後輩の {nickname} - hubot に面通ししよう！
 #   hubot {nickname} のアダ名何ですか?  - ものしりな hubot にアダ名を教えiてもらおう！
-#   hubot {アダ名} は {nickname} のアダ名  - hubot にアダ名を教えるよ！
-#   hubot {アダ名} は {nickname} のアダ名じゃない  - hubot にアダ名が間違いだったことをを教えるよ！
-
-aisatsu = [# {{{
+#   hubot {アダ名} は {nickname} のアダ名です  - hubot にアダ名を教えるよ！
+#   hubot {アダ名} は {nickname} のアダ名じゃないです  - hubot にアダ名が間違いだったことをを教えるよ！
+#   {アダ名 or nickname}++  - イイネ！
+#   {アダ名 or nickname}--  - ヨクナイネ！
+
+AISATSU = [# {{{
   'よろしくな'
   'まいど'
   '元気か？'
 ]# }}}

-nandeTameguchi = [# {{{
+NANDE_TAMEGUCHI = [# {{{
   'つか、何でタメ口なん？'
   '口のききかたに気をつけや'
 ]# }}}
+
+COUNT_PLUS = [
+  'すげーな！'
+  '人気でてるぞ'
+]

 # {{{ existsUser
 existsUser = (robot, msg, name) ->
@@ -57,6 +64,16 @@
   usersInfo[name][key]
 # }}}

+
+whoIsThis = (robot, msg, name) -># {{{
+  return name if existsUser robot, msg, name
+  gNicknames = getSenpaiStorage robot, msg, 'NICKNAMES'
+  gNicknames ||= {}
+  return gNicknames[name] if gNicknames[name]
+  return null
+# }}}
+
+
 # {{{ Keigo
 REGEXP_KEIGO = '(です|デス|desu|ます|マス|masu|っす|ッス)か?[\?？!！]?'

@@ -67,7 +84,7 @@
   str.replace new RegExp('' + REGEXP_KEIGO + '$'), ''

 checkKeigo = (robot, msg) =>
-  msg.send msg.random nandeTameguchi unless isKeigo robot, msg
+  msg.send msg.random NANDE_TAMEGUCHI unless isKeigo robot, msg
 # }}}

 module.exports = (robot) ->
@@ -81,7 +98,7 @@
       msg.send "#{msg.message.user.name} こいつ誰？ > all"
       checkKeigo robot, msg
       return
-    msg.send msg.random aisatsu
+    msg.send msg.random AISATSU
     checkKeigo robot, msg
 # }}}

@@ -191,3 +208,22 @@
     setUserInfo robot, msg, realname, 'NICKNAMES', newNicknames
     checkKeigo robot, msg
 # }}}
+
+# {{{ plusplus
+  robot.hear /([^ ]+)\+\+/i, (msg) ->
+    name = msg.match[1]
+
+    user = whoIsThis robot, msg, name
+    unless user?
+      msg.send "#{realname} って誰？先に教えて"
+      return
+
+    count = getUserInfo robot, msg, user, 'COUNT'
+    count ||= 0
+    count++
+    setUserInfo robot, msg, user, 'COUNT', count
+
+    msg.send "#{name}: #{count}点 " + msg.random COUNT_PLUS
+# }}}
+
+


Shakespeare inslut

Shakespeare inslut

--- scripts/shaker.coffee
+++ scripts/shaker.coffee
@@ -6,7 +6,7 @@
 #
 #
 # Commands:
-#   hubot (do|don't|are you) (insult2|insulting) me
+#   hubot (do|don't|are you) (Shakespeare insult|insulting) me
 #
 #
 # Author:
@@ -16,7 +16,7 @@

 module.exports = (robot) ->

-  robotInsultRegex = new RegExp("(#{robot.name}|.*)? ?(insult2|insulting) ?(#{robot.name}|.*)?", "i")
+  robotInsultRegex = new RegExp("(#{robot.name}|.*)? ?(Shakespeare insult|insulting) ?(#{robot.name}|.*)?", "i")

   insultFunction = (res) ->
     robot.http(insultUrl)


position cursor at end when filling with previous input

--- editor.coffee
+++ editor.coffee
@@ -120,6 +120,7 @@
     previous_context = context_at_offset cm.lead_cell, -1
     if previous_context?
       cm.setValue previous_context.editor.getValue()
+      cm.setCursor(line: cm.lineCount() - 1)
     else
       CodeMirror.Pass


#1518 only soma click when soma clicking is allowed

--- app/assets/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.coffee
+++ app/assets/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.coffee
@@ -161,7 +161,7 @@

   addNode : (position, rotation, centered) =>

-    if @model.user.get("newNodeNewTree") == true
+    if @model.settings.somaClickingAllowed and @model.user.get("newNodeNewTree")
       @model.skeletonTracing.createNewTree()

     if not @model.skeletonTracing.getActiveNode()?


Few lines were translated on russian (#5512)

* Few lines were translated on russian

* Update ru.coffee

Leading spaces removed to respect syntax 47

* Update ru.coffee (2)

Leading spaces removed to respect syntax of the file

--- app/locale/ru.coco.coffee
+++ app/locale/ru.coco.coffee
@@ -23,7 +23,7 @@
 #    century_skills_subtitle3: "Collaboration"
 #    century_skills_subblurb3: "Throughout the game, there are opportunities for students to collaborate when they get stuck and to work together using our pair programming guide."
 #    century_skills_quote4: "I’ve always had aspirations of designing video games and learning how to code ... this is giving me a great starting point."
-#    century_skills_subtitle4: "Communication"
+    century_skills_subtitle4: "Коммуникация"
 #    century_skills_subblurb4: "Coding requires kids to practice new forms of communication, including communicating with the computer itself and conveying their ideas using the most efficient code."
 #    classroom_in_box_title: "We Strive To:"
 #    classroom_in_box_blurb1: "Engage every student so that they believe coding is for them."
@@ -37,14 +37,15 @@
 #    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
 #    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
     featured_partners_title1: "Зарекомендован в"
-#    featured_partners_title2: "Awards & Partners"
+    featured_partners_title2: "Награды и Партнеры"
     featured_partners_blurb1: "Совет колледжа одобрил провайдера"
     featured_partners_blurb2: "Лучшая креативная вещь для студентов"
     featured_partners_blurb3: "Лучший выбор для обучения"
+
 #    featured_partners_blurb4: "Code.org Official Partner"
 #    featured_partners_blurb5: "CSforAll Official Member"
 #    featured_partners_blurb6: "Hour of Code Activity Partner"
-#    for_leaders_title: "For School Leaders"
+    for_leaders_title: "Для лидеров школы"
 #    for_leaders_blurb: "A Comprehensive, Standards-Aligned Computer Science Program"
 #    for_leaders_subtitle1: "Easy Implementation"
 #    for_leaders_subblurb1: "A web-based program that requires no IT support. Get started in under 5 minutes using Google or Clever Single Sign-On (SSO)."
@@ -58,17 +59,17 @@
     for_teachers_blurb: "Инструменты для раскрытия потенциала ученика"
 #    for_teachers_subtitle1: "Project-Based Learning"
 #    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
-#    for_teachers_subtitle2: "Teacher Dashboard"
+    for_teachers_subtitle2: "Рабочая панель учителя"
 #    for_teachers_subblurb2: "View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
 #    for_teachers_subtitle3: "Built-in Assessments"
 #    for_teachers_subblurb3: "Personalize instruction and ensure students understand core concepts with formative and summative assessments."
 #    for_teachers_subtitle4: "Automatic Differentiation"
 #    for_teachers_subblurb4: "Engage all learners in a diverse classroom with practice levels that adapt to each student's learning needs."
 #    game_based_blurb: "CodeCombat is a game-based computer science program where students type real code and see their characters react in real time."
-#    get_started: "Get started"
+    get_started: "Начать"
 #    global_title: "Join Our Global Community of Learners and Educators"
-#    global_subtitle1: "Learners"
-#    global_subtitle2: "Lines of Code"
+    global_subtitle1: "Учащиеся"
+    global_subtitle2: "Строки с кодом"
 #    global_subtitle3: "Teachers"
 #    global_subtitle4: "Countries"
 #    go_to_my_classes: "Go to my classes"


Removed FactlinkConfig

Removed FactlinkConfig

--- js-library/app/js/publisher_poc/nu.nl.coffee
+++ js-library/app/js/publisher_poc/nu.nl.coffee
@@ -6,23 +6,9 @@
   */
 (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()})("$script",this,function(){function v(e,t){for(var n=0,r=e.length;n<r;++n)if(!t(e[n]))return f;return 1}function m(e,t){v(e,function(e){return!t(e)})}function g(e,t,a){function d(e){return e.call?e():r[e]}function b(){if(!--p){r[h]=1,c&&c();for(var e in s)v(e.split("|"),d)&&!m(s[e],d)&&(s[e]=[])}}e=e[l]?e:[e];var f=t&&t.call,c=f?t:a,h=f?e.join(""):t,p=e.length;return setTimeout(function(){m(e,function(e){if(e===null)return b();if(u[e])return h&&(i[h]=1),u[e]==2&&b();u[e]=1,h&&(i[h]=1),y(!n.test(e)&&o?o+e+".js":e,b)})},0),g}function y(n,r){var i=e.createElement("script"),s=f;i.onload=i.onerror=i[d]=function(){if(i[h]&&!/^c|loade/.test(i[h])||s)return;i.onload=i[d]=null,s=1,u[n]=2,r()},i.async=1,i.src=n,t.insertBefore(i,t.firstChild)}var e=document,t=e.getElementsByTagName("head")[0],n=/^https?:\/\//,r={},i={},s={},o,u={},a="string",f=!1,l="push",c="DOMContentLoaded",h="readyState",p="addEventListener",d="onreadystatechange";return!e[h]&&e[p]&&(e[p](c,function b(){e.removeEventListener(c,b,f),e[h]="complete"},f),e[h]="loading"),g.get=y,g.order=function(e,t,n){(function r(i){i=e.shift(),e.length?g(i,r):g(i,t,n)})()},g.path=function(e){o=e},g.ready=function(e,t,n){e=e[l]?e:[e];var i=[];return!m(e,function(e){r[e]||i[l](e)})&&v(e,function(e){return r[e]})?t():!function(e){s[e]=s[e]||[],s[e][l](t),n&&n(i)}(e.join("|")),g},g.done=function(e){g([null],e)},g})
 `
-localConfig =
-  api: "http://localhost:3000"
-  lib: "http://localhost:8000/lib/dist"
-  srcPath: "/factlink.core.js"
-  url: window.location.href
-
-remoteConfig =
-  api: "https://factlink-testserver.inverselink.com:443"
-  lib: "https://factlink-static-testserver.inverselink.com:443/lib/dist"
-  srcPath: "/factlink.core.min.js"
-  url: window.location.href
-
-window.FactlinkConfig = localConfig
-
 $script(['//code.jquery.com/jquery-1.10.2.min.js',
          '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js',
-         "#{window.FactlinkConfig.lib}/factlink_loader_basic.js"
+         "#{window.FactlinkConfig.lib}/factlink_loader_basic.#{window.FactlinkConfig.minified}js"
          ], 'myBundle')

 $script.ready 'myBundle', ->


refactor toolpad

refactor toolpad

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -253,23 +253,7 @@
 			@hideTooltip()
 		$('body').delegate '.content', 'mouseleave', (e)=>
 			@hideTooltip()
-	# delegateEditorToolpadEvents: ()->
-	delegateEditorEvents: ()->
-		self = @
-		$('#editor').delegate '> h1,h2,p,pre,code,figure', 'mousemove', (e)->
-			return if self.status.empty || self.status.new
-			$(@).addClass('hovered').siblings().removeClass('hovered')
-			parentOffset = $(@).parent().offset()
-			thisOffset = $(@).offset()
-			height = $(@).outerHeight()
-
-			self.updateTooltipStatus $(@)
-			self.showTooltip()
-			self.setTooltipTop thisOffset.top - parentOffset.top
-		.delegate '> h1,h2,p,pre,code,figure', 'mouseleave', (e)->
-			self.update()
-
-		# toolpad
+	delegateEditorToolpadEvents: ()->
 		$('body').delegate '#editor', 'mouseup', (e)=>
 			setTimeout =>
 				@detectToShowToolpad()
@@ -300,6 +284,20 @@
 					range.selectNode node[0]
 					self.linkRange = range
 				self.applyOrCancelUrl self.linkRange, '' unless $(@).val()
+	delegateEditorEvents: ()->
+		self = @
+		$('#editor').delegate '> h1,h2,p,pre,code,figure', 'mousemove', (e)->
+			return if self.status.empty || self.status.new
+			$(@).addClass('hovered').siblings().removeClass('hovered')
+			parentOffset = $(@).parent().offset()
+			thisOffset = $(@).offset()
+			height = $(@).outerHeight()
+
+			self.updateTooltipStatus $(@)
+			self.showTooltip()
+			self.setTooltipTop thisOffset.top - parentOffset.top
+		.delegate '> h1,h2,p,pre,code,figure', 'mouseleave', (e)->
+			self.update()
 	handleTitleKeyDown: (e)->
 		switch e.keyCode
 			when 13


paste to editor

paste to editor

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -218,6 +218,19 @@
 			@selection()
 		.on 'mouseup', (e)=>
 			@selection()
+		.on 'paste', (e)=>
+			e.preventDefault()
+			raw = e.clipboardData.getData('text/html') || e.clipboardData.getData('text')
+			pasteElement = document.createElement 'p'
+			pasteElement.innerHTML = raw
+			data = $(pasteElement)
+			# children = data.children()
+			# children.each ->
+			# 	$(this).removeAttr('style').find('*').removeAttr('style')
+			result = data.html()
+				.replace( new RegExp('h[0-9]', 'ig'), 'h1' )
+				# .clearTagsUnless(['p', 'h1', 'a', 'pre'])
+			document.execCommand 'insertHtml', false, result
 		.on 'askid', =>
 			if @askid
 				@askid( @ ) unless @status.connecting


fix indent

fix indent

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -165,31 +165,31 @@
             production: true

     uglify:
-          options:
-            preserveComments: require 'uglify-save-license'
-          main:
-            options:
-              banner: "/*! Copyright (c) 2013 -2014 Shinnosuke Watanabe | MIT License */\n"
-              compress:
-                global_defs:
-                  DEBUG: false
-                dead_code: true
-            src: '<%= coffee.dist.dest %>'
-            dest: '<%= uglify.main.src %>'
-          bower:
-            options:
-              compress:
-                # For /*cc_on!*/ comments
-                dead_code: false
-            files: [
-              expand: true
-              cwd: '<%= bower.options.targetDir %>'
-              src: [
-                '{,*/,*/*/}*.js',
-                '!{,*/,*/*/}*{.min,-min}.js', '!debug/{,*/}*.js'
-              ]
-              dest: '<%= bower.options.targetDir %>'
-            ]
+      options:
+        preserveComments: require 'uglify-save-license'
+      main:
+        options:
+          banner: "/*! Copyright (c) 2013 -2014 Shinnosuke Watanabe | MIT License */\n"
+          compress:
+            global_defs:
+              DEBUG: false
+            dead_code: true
+        src: '<%= coffee.dist.dest %>'
+        dest: '<%= uglify.main.src %>'
+      bower:
+        options:
+          compress:
+            # For /*cc_on!*/ comments
+            dead_code: false
+        files: [
+          expand: true
+          cwd: '<%= bower.options.targetDir %>'
+          src: [
+            '{,*/,*/*/}*.js',
+            '!{,*/,*/*/}*{.min,-min}.js', '!debug/{,*/}*.js'
+          ]
+          dest: '<%= bower.options.targetDir %>'
+        ]

     compass:
       dist:
@@ -254,7 +254,7 @@
           user:
             name: 'shinnn'
         src: '**/*'
-
+
   grunt.registerTask 'default', [
     'analysis'
     'compass'


Fixes bug 1011571 - Compile prod CSS into app directory

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -111,8 +111,9 @@
     autoprefixer:
       options:
         browsers: ["last 2 versions"]
-      src: ".static/css/app.ltr.css"
-      dest: "app/compiled/app.ltr.css"
+      build:
+        src: ".static/css/app.ltr.css"
+        dest: "app/compiled/app.ltr.css"

     jshint:
       all: [
@@ -187,6 +188,6 @@
   # Build for Production
   grunt.registerTask "build", [
     "less:production"
-    "autoprefixer"
+    "autoprefixer:build"
   ]
   return


Grunt: Allow CORS for connect server

Grunt: Allow CORS for connect server

Useful to serve .yaml files to http://fbpspec.run

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,8 @@
 path = require 'path'
+
+allowCorsMiddleware = (req, res, next) ->
+  res.setHeader 'Access-Control-Allow-Origin', '*'
+  next()

 module.exports = ->
   # Project configuration
@@ -58,6 +62,9 @@
         options:
           port: 8000
           livereload: true
+          middleware: (connect, options, middlewares) ->
+            middlewares.unshift allowCorsMiddleware
+            return middlewares

     # Coding standards
     yamllint:


Add pushTo

Add pushTo

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -2,6 +2,7 @@
   grunt.initConfig
     bump:
       files: ['package.json']
+      pushTo: 'origin'
       push: false
     exec:
       test:


Copy firefox dist scripts to correct locations

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -454,7 +454,8 @@
               'data/**/freedom-module.json'
               '!generic_core/freedom-module.json'
               'data/**/*.static.js'
-              'data/scripts/get_logs.js'
+              'data/generic_ui/scripts/get_logs.js'
+              'data/scripts/content-proxy.js'
               '!**/*spec*'

               'data/bower/webcomponentsjs/webcomponents.min.js'


Add forge to the CCA build for cloud support

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -463,6 +463,9 @@
             'freedom-social-quiver/socketio.quiver.js'

             'freedom-for-chrome/freedom-for-chrome.js'
+
+            # for cloud social provider
+            'forge-min/forge.min.js'
           ].concat(uiDistFiles, coreDistFiles, universalDistFiles)
           dest: ccaDistPath
         }
@@ -603,6 +606,7 @@
       Rule.copyLibs
         npmLibNames: [
           'freedom-for-chrome'
+          'forge-min'
         ]
         pathsFromDevBuild: [
           'generic_core'


chore(build): copy ts file to lib

chore(build): copy ts file to lib

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,7 +10,7 @@
     copy:
       main:
         files: [
-            {expand: true, cwd: 'src/', src: ['**/*.js'], dest: 'lib/'}
+            {expand: true, cwd: 'src/', src: ['**/*.js', '**/*.ts'], dest: 'lib/'}
         ]

     newer:
@@ -52,7 +52,7 @@
           'src/**/*.js'
           'test/*.coffee'
         ]
-
+

   #grunt.loadTasks('tasks')


added concat tasks to Grunt

added concat tasks to Grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,6 +22,13 @@
             less:
                 files: ['src/less/site.less', 'src/less/responsive.breadcrumbs.less']
                 tasks: ['less']
+        concat:
+            rwd:
+                src: ['assets/dependencies/modernizr/modernizr.js', 'assets/dependencies/respond/respond.min.js']
+                dest: 'assets/compiled/rwd.js'
+            libs:
+                src: ['assets/dependencies/jquery/jquery.min.js', 'assets/dependencies/underscore/underscore-min.js', 'assets/dependencies/bootstrap/dist/js/bootstrap.min.js']
+                dest: 'assets/compiled/libs.js'
         notify_hooks:
             options:
                 enabled: true


Add nginx task

Add nginx task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,6 +11,7 @@
     # ----
     # Start with `grunt nginx:start`
     nginx:
+      tasks: ['nginx']
       options:
         config: 'nginx.development.conf'
         prefix: './'


use my repository for clsi too

use my repository for clsi too

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,7 +15,8 @@
 	repo: "https://github.com/sharelatex/document-updater-sharelatex.git"
 }, {
 	name: "clsi"
-	repo: "https://github.com/sharelatex/clsi-sharelatex.git"
+	repo: "https://github.com/dwrensha/clsi-sharelatex.git"
+	branch: "sandstorm-app"
 }, {
 	name: "filestore"
 	repo: "https://github.com/sharelatex/filestore-sharelatex.git"


chore(build): add *docs* task to *build:release*

  - remove redundant *docs* task from *default*

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -692,6 +692,7 @@
     'concat:release_js'
     'uglify'
     'index:release'
+    'docs'
   ]

   grunt.registerTask 'build', [
@@ -761,7 +762,6 @@
   # The default task is to `update` and `build` all versions.
   grunt.registerTask 'default', [
     'build'
-    'docs'
     'open:release'
     'connect:release'
   ]


Flatten output files

Flatten output files

Fix #49
Fix #47

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -69,6 +69,7 @@
       js:
         files: [
           expand: true
+          flatten: true
           cwd: 'src/'
           src: ['**/*.js']
           dest: 'build/arquivos/'
@@ -76,6 +77,7 @@
       css:
         files: [
           expand: true
+          flatten: true
           cwd: 'src/'
           src: ['**/*.css']
           dest: 'build/arquivos/'
@@ -85,6 +87,7 @@
       main:
         files: [
           expand: true
+          flatten: true
           cwd: 'src/'
           src: ['**/*.coffee']
           dest: 'build/arquivos/'
@@ -99,6 +102,7 @@
           importer: tildeImporter
         files: [
           expand: true
+          flatten: true
           cwd: 'src/'
           src: ['**/*.scss']
           dest: 'build/arquivos/'
@@ -113,6 +117,7 @@
           sourceMapRoot: '../src/'
         files: [
           expand: true
+          flatten: true
           cwd: 'src/'
           src: ['**/*.scss']
           dest: 'build/arquivos/'
@@ -123,6 +128,7 @@
       main:
         files: [
           expand: true
+          flatten: true
           cwd: 'src/'
           src: ['**/*.less']
           dest: "build/arquivos/"
@@ -132,6 +138,7 @@
     cssmin:
       main:
         expand: true
+        flatten: true
         cwd: 'src/'
         src: ['**/*.css', '!**/*.min.css']
         dest: 'build/arquivos/'
@@ -150,6 +157,7 @@
       main:
         files: [{
           expand: true
+          flatten: true
           cwd: 'build/'
           src: ['**/*.js', '!**/*.min.js']
           dest: 'build/arquivos/'
@@ -166,6 +174,7 @@
       main:
         files: [
           expand: true
+          flatten: true
           cwd: 'build/'
           src: ['**/*.{png,jpg,gif}']
           dest: 'build/'


Annotate tasks

Annotate tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -313,11 +313,16 @@
           data:
             scripts: jsFiles

-  # Build tasks (NOTE: order is significant!)
+  ## Build tasks
+
+  # Usually you just want to run `grunt` to get the deployed code
   grunt.registerTask 'default', ['install', 'source', 'compile', 'build']
+  # Or spin up a Harp server for development by running `grunt watch`
+  grunt.registerTask 'watch', ['index:source', 'exec:harpServer']
+  # Or just install dependencies
   grunt.registerTask 'install', ['exec:bower']
-  grunt.registerTask 'watch', ['index:source', 'exec:harpServer']
-
+
+  # Make sure our HTML is referencing all our scripts and styles
   grunt.registerTask 'source', [
     'clean:source' # Clean the source directory first
     'coffeelint' # Then check CoffeeScripts are style-compliant
@@ -325,8 +330,10 @@
     'index:source' # Build `index.html`
   ]

-  # TODO review and include karma
-  #grunt.registerTask 'source', ['copy:source', 'coffeelint', 'index:source', 'karmaconfig', 'karma:continuous']
+  # TODO include karma in development
+  #grunt.registerTask 'source', ['karmaconfig', 'karma:continuous']
+
+  # Compile for delivery (but not minified)
   grunt.registerTask 'compile', [
     'clean:compile' # Clean the compile directory
     'clean:source' # Clean the source directory as well because we need to compile from source
@@ -337,6 +344,7 @@
     'copy:assetsToCompile' # As name
   ]

+  # The ultimate deployment package
   grunt.registerTask 'build', [
     # Clean up first
     'clean:build'


Add testing task

Add testing task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -66,6 +66,15 @@

   grunt.loadTasks "tasks"

+  grunt.registerTask "message", "Display a message", ( arg ) ->
+    grunt.log.writeln switch
+      when "one" then "Hey ! This is task message:one !"
+      when "two" then "Hey ! This is task message:two !"
+      when "three" then "Hey ! This is task message:three !"
+      when "four" then "Hey ! This is task message:four !"
+      when "five" then "Hey ! This is task message:five !"
+      else "Hey ! This is task message:default !"
+
   grunt.registerTask "default", [
     "clear"
     "coffeelint"


clean up gruntfile

clean up gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -36,20 +36,26 @@

   # Project configuration.
   grunt.config.init
-    sizereport:
-      "Core":
-        "Compressed stylesheet": [ rootFile + ".min.css" ]
-        "Compressed javascript": [ rootFile + ".min.js" ]
-      "Stylesheets":
-        "structure": [structureFile  + ".min.css"]
-        "theme": [themeFile + ".min.css"]
-      "Custom font-awesome":
-        "Stylesheet": [ "compiled/customfont/fontawesome.css" ]
-        "TTF fonts": [ "compiled/customfont/fontawesome-webfont.ttf" ]
-        "EOT fonts": [ "compiled/customfont/fontawesome-webfont.eot" ]
-        "WOFF fonts": [ "compiled/customfont/fontawesome-webfont.woff" ]
-      "Images":
-        "Image files": [ "compiled/images" ]
+
+    global:
+      dirs: dirs
+      names: names
+      files:
+        license: "LICENSE.txt"
+
+
+      # other version information is added via the asyncConfig helper that
+      # depends on git commands (eg ver.min, ver.header)
+      ver:
+        official: verOfficial
+        min: "/*! umobi v<%= build_sha %> umobi.com !*/"
+        gitLongSha: "git log -1 --format=format:\"Git Build: SHA1: %H <> Date: %cd\""
+        gitShortSha: "git log -1 --format=format:\"%H\""
+
+      shas: {}
+
+
+

     jshint: readCoffee "build/jshint.coffee"

@@ -94,17 +100,6 @@
     # NOTE the keys are filenames which, being stored as variables requires that we use
     #      key based assignment. See below.
     uglify: `undefined`
-    cssmin:
-      all:
-        src: rootFile + ".css"
-        dest: rootFile  + ".min.css"
-      structure:
-        src: structureFile + ".css"
-        dest: structureFile  + ".min.css"
-      theme:
-        src: themeFile + ".css"
-        dest: themeFile + ".min.css"
-    compress: readCoffee "build/compress.coffee"

     # JS config, mostly the requirejs configuration
     # full example: https://github.com/jrburke/r.js/blob/master/build/example.build.js
@@ -124,31 +119,41 @@
           cssIn: "css/structure/umobi.css"
           out: structureFile + ".compiled.css"

-    global:
-      dirs: dirs
-      names: names
-      files:
-        license: "LICENSE.txt"
+    # config for grunt-css
+    cssmin:
+      all:
+        src: rootFile + ".css"
+        dest: rootFile  + ".min.css"
+      structure:
+        src: structureFile + ".css"
+        dest: structureFile  + ".min.css"
+      theme:
+        src: themeFile + ".css"
+        dest: themeFile + ".min.css"

-
-      # other version information is added via the asyncConfig helper that
-      # depends on git commands (eg ver.min, ver.header)
-      ver:
-        official: verOfficial
-        min: "/*! umobi v<%= build_sha %> umobi.com !*/"
-        gitLongSha: "git log -1 --format=format:\"Git Build: SHA1: %H <> Date: %cd\""
-        gitShortSha: "git log -1 --format=format:\"%H\""
+    # config for grunt-contrib-compress
+    compress: readCoffee "build/compress.coffee"

-      shas: {}
+    sizereport:
+      "Core":
+        "Compressed stylesheet": [ rootFile + ".min.css" ]
+        "Compressed javascript": [ rootFile + ".min.js" ]
+      "Stylesheets":
+        "structure": [structureFile  + ".min.css"]
+        "theme": [themeFile + ".min.css"]
+      "Custom font-awesome":
+        "Stylesheet": [ "compiled/customfont/fontawesome.css" ]
+        "TTF fonts": [ "compiled/customfont/fontawesome-webfont.ttf" ]
+        "EOT fonts": [ "compiled/customfont/fontawesome-webfont.eot" ]
+        "WOFF fonts": [ "compiled/customfont/fontawesome-webfont.woff" ]
+      "Images":
+        "Image files": [ "compiled/images" ]

-
   # MIN configuration
   uglify = { js: { files: {} } }
   uglify.options = { banner: "<%= global.ver.min %>" }
   uglify.js.files[ rootFile + ".min.js" ] = [rootFile + ".js"]
   grunt.config.set "uglify",uglify
-
-

   # authors task
   grunt.loadNpmTasks "grunt-git-authors"


use environments option of es6-transpiler

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -48,6 +48,8 @@
           'lib/cli.js': ['src/cli-es6.js']

     es6transpiler:
+      options:
+        environments: ['node', 'browser', 'mocha']
       main:
         options:
           globals:
@@ -56,10 +58,6 @@
           'lib/cli.js': ['lib/cli.js']
           'lib/isogram.js': ['lib/isogram.js']
       tests:
-        options:
-          globals:
-            describe: false
-            it: false
         files: [
           expand: true
           src: ['test/**/*.js']


remove maintenance task and jsut keep static page

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -158,15 +158,15 @@
       dist:
         src: 'bower_components/requirejs/require.js'
         dest: 'dist/scripts/require.js'
+      maintenance:
+        src: 'src/maintenance.html'
+        dest: 'dist/maintenance.html'
       fonts:
         expand: true
         filter: 'isFile'
         flatten: true
         src: ['bower_components/bootstrap/fonts/**']
         dest: 'dist/fonts/'
-      maintenance:
-        src: 'src/maintenance.html'
-        dest: 'dist/index.html'

     # Clean
     clean:
@@ -210,6 +210,7 @@
           collapseWhitespace: true
         files:
           'dist/index.html': 'dist/index.html'
+          'dist/maintenance.html': 'dist/maintenance.html'

     # Imagemin
     imagemin:
@@ -248,6 +249,7 @@
   grunt.registerTask 'dist', [
     'requirejs'
     'copy:dist'
+    'copy:maintenance'
     'copy:fonts'
     'targethtml:dist'
     'clean'
@@ -256,17 +258,12 @@
     'imagemin'
   ]

-  # Maintenance
-  # -----
-  grunt.registerTask 'maintenance', [
-    'copy:maintenance'
-  ]
-
   # Default
   # -----
   grunt.registerTask 'default', [
     'requirejs'
     'copy:dist'
+    'copy:maintenance'
     'copy:fonts'
     'targethtml:dist'
     'clean'


Editor: add CTRL+W key binding to close current tab.

--- client/Ace/ace.coffee
+++ client/Ace/ace.coffee
@@ -105,6 +105,7 @@
       @addKeyCombo "fullscreen", "Ctrl-Enter", => @getDelegate().toggleFullscreen()
       @addKeyCombo "gotoLine", "Ctrl-G", @bound "showGotoLine"
       @addKeyCombo "saveAll", "Ctrl-Alt-S", @bound "saveAllFiles"
+      @addKeyCombo "closeTab", "Ctrl-W", "Ctrl-W", @bound "closeTab"
       @addKeyCombo "settings", "Ctrl-,", noop # ace creates a settings view for this shortcut, overriding it.

   showFindReplaceView: (openReplaceView) ->
@@ -128,6 +129,11 @@

   isContentChanged: -> @contentChanged
   isCurrentContentChanged:-> @getContents() isnt @lastSavedContents
+
+  closeTab: ->
+    aceView   = @getDelegate()
+    {tabView} = aceView.getDelegate()
+    tabView.removePane_ tabView.getActivePane()

   ###
   FS REQUESTS


Ace: Remove unused options parameter from requestSaveAs

--- client/Ace/ace.coffee
+++ client/Ace/ace.coffee
@@ -178,8 +178,8 @@
     localSync.on 'LocalContentCouldntSynced', (file) =>
       @notify 'File coudn\'t be synced to remote please try again...', null, null, 5000

-  requestSaveAs: (options) ->
-    @emit 'ace.requests.saveAs', @getContents(), options
+  requestSaveAs: ->
+    @emit 'ace.requests.saveAs', @getContents()

   fetchContents:(callback)->
     file = @getData()


Editor: fix syntax highlighting

Editor: fix syntax highlighting

--- client/Ace/ace.coffee
+++ client/Ace/ace.coffee
@@ -267,7 +267,7 @@
           mode = name

       syntaxChoice = @appStorage.getValue "syntax_#{ext}"
-      mode = syntaxChoice or "text"
+      mode = syntaxChoice or mode or "text"

     require ["ace/mode/#{mode}"], ({Mode})=>
       @editor.getSession().setMode new Mode


Connect page: trigger 'change' after New and Delete

--- client/connect.coffee
+++ client/connect.coffee
@@ -93,8 +93,8 @@
     $new.click ->
       $list.find('option').attr 'selected', false
       $list.append '<option selected>127.0.0.1</option>'
-      $host.select(); storeFavs(); false
-    $delete.click -> $list.find(':selected').remove(); storeFavs(); false
+      $host.select(); storeFavs(); $list.change(); false
+    $delete.click -> $list.find(':selected').remove(); storeFavs(); $list.change(); false
     $list.on 'change', -> # triggered after selection changes
       storeFavs(); $s = $list.find ':selected'; n = $s.length
       $name.add($host).add($port).attr 'disabled', n != 1


Never fold while editing a message

Never fold while editing a message

--- client/message.coffee
+++ client/message.coffee
@@ -88,7 +88,9 @@
     search: "tag:#{@key}"

 Template.registerHelper 'folded', ->
-  (not here @_id) and messageFolded.get @_id
+  (messageFolded.get @_id) and
+  (not here @_id) and                       # never fold if top-level message
+  (not Template.instance()?.editing?.get()) # never fold if editing

 Template.rootHeader.helpers
   root: ->


Fix for legacy binaries with no checksums

--- backend/filesystem.coffee
+++ backend/filesystem.coffee
@@ -127,6 +127,10 @@

     # Check if a file corresponding to given checksum already exists.
     fileExistsLocally: (checksum, callback) ->
+        # For legacy binaries
+        if not checksum or checksum is ''
+            return callback null, false
+
         pouch.binaries.get checksum, (err, binaryDoc) ->
             if err
                 callback err


fixes an issue I had with backbone stickit and 1password with the expiry date not parsing correctly

--- src/jquery.payment.coffee
+++ src/jquery.payment.coffee
@@ -401,8 +401,7 @@
   $.payment.cardExpiryVal($(this).val())

 $.payment.cardExpiryVal = (value) ->
-  value = value.replace(/\s/g, '')
-  [month, year] = value.split('/', 2)
+  [month, year] = value.split(/[\s\/]+/, 2)

   # Allow for year shortcut
   if year?.length is 2 and /^\d+$/.test(year)


Fix #131: limit length of CVC on paste

Fix #131: limit length of CVC on paste

--- src/jquery.payment.coffee
+++ src/jquery.payment.coffee
@@ -258,7 +258,16 @@
     e.preventDefault()
     setTimeout -> $target.val(value.replace(/\d\s\/\s$/, ''))

-#  Restrictions
+# Format CVC
+
+reFormatCVC = (e) ->
+  setTimeout ->
+    $target = $(e.currentTarget)
+    value   = $target.val()
+    value   = value.replace(/\D/g, '')[0...4]
+    $target.val(value)
+
+# Restrictions

 restrictNumeric = (e) ->
   # Key event is for a browser shortcut
@@ -339,6 +348,9 @@
 $.payment.fn.formatCardCVC = ->
   @payment('restrictNumeric')
   @on('keypress', restrictCVC)
+  @on('paste', reFormatCVC)
+  @on('change', reFormatCVC)
+  @on('input', reFormatCVC)
   this

 $.payment.fn.formatCardExpiry = ->


Clear up binding enabling and disabling

Clear up binding enabling and disabling

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -604,12 +604,19 @@
     @cancelPendingState()
     @clearQueuedKeystrokes()

-    binding.enabled = false for binding in bindingsToDisable
-
+    enableBindings = (bindings) ->
+      if bindings?
+        binding.enabled = true for binding in bindings
+
+    disableBindings = (bindings) ->
+      if bindings?
+        binding.enabled = false for binding in bindings
+
+    disableBindings(bindingsToDisable)
     for event in eventsToReplay
-      @handleKeyboardEvent(event)
+      @handleKeyboardEvent(event, true)
       if bindingsToDisable? and not @pendingPartialMatches?
-        binding.enabled = true for binding in bindingsToDisable
+        enableBindings(bindingsToDisable)
         bindingsToDisable = null

     atom?.assert(not bindingsToDisable?, "Invalid keymap state")


remove vestigial sprite.step

remove vestigial sprite.step

--- src/layout/sprites.coffee
+++ src/layout/sprites.coffee
@@ -104,10 +104,4 @@

   reset: (world, args) ->
     ['position', 'direction'].map (key) -> world.put key, undefined
-
-  step: (world, args) ->
-    local = world.get('programs')
-    return unless world.is_world local
-    my.assert signal = local.call('next'), "No next signal"
-    world.call 'perform', signal
 }


define range

define range

--- jsapp/xlform/src/model.configs.coffee
+++ jsapp/xlform/src/model.configs.coffee
@@ -150,6 +150,9 @@
     date:
       label:
         value: "Enter a date"
+    range:
+      label:
+        value: "Choose a range"
     calculate:
       calculation:
         value: ""
@@ -177,6 +180,7 @@
       ["text", "Text"], # expects text
       ["integer", "Integer"], #e.g. 42
       ["decimal", "Decimal"], #e.g. 3.14
+      ["range", "Range"], #e.g. 1-5
       ["geopoint", "Geopoint (GPS)"], # Can use satelite GPS coordinates
       ["geotrace", "Geotrace (GPS)"], # Can use satelite GPS coordinates
       ["geoshape", "Geoshape (GPS)"], # Can use satelite GPS coordinates


Removed `bufferize` from `transforms.coffee`.

--- src/lib/transforms.coffee
+++ src/lib/transforms.coffee
@@ -1,14 +1,8 @@
-bufferize = (array) ->
-  buffer = new Buffer(array.length)
-  for b, i in array
-    buffer[i] = b
-  buffer
-
 transforms = exports.transforms =
   str: (encoding, parsing, field, value) ->
     if parsing
       if not (value instanceof Buffer)
-        value = bufferize(value)
+        value = new Buffer(value)
       if /^ascii$/i.test(encoding)
         # Broken and waiting on [297](http://github.com/ry/node/issues/issue/297).
         # If the top bit is set, it is not ASCII, so we zero the value.


Grid/Column/Row spacing with layout.fill bug fixed

--- impl/base/utils/grid.coffee
+++ impl/base/utils/grid.coffee
@@ -152,7 +152,7 @@
 		last = rowsPositions[i] += last
 	rowsPositions[i-1] -= rowSpacing
 	if autoHeight
-		freeColumnsSpace = 0
+		freeRowsSpace = 0
 	else
 		lastRowPosition = rowsPositions[i-1]
 		rowsPositions[i-1] = Math.max rowsPositions[i-1], effectItem._height
@@ -207,11 +207,17 @@
 		if layout
 			# set width
 			if layout._fillWidth and not autoWidth
-				child.width = columnsPositions[column] - columnX - leftMargin - rightMargin
-
+				width = columnsPositions[column] - columnX - leftMargin - rightMargin
+				if column isnt lastColumn
+					width -= columnSpacing
+				child.width = width
+
 			# set height
 			if layout._fillHeight and not autoHeight
-				child.height = rowsPositions[row] - rowY - topMargin - bottomMargin
+				height = rowsPositions[row] - rowY - topMargin - bottomMargin
+				if row isnt lastRow
+					height -= rowSpacing
+				child.height = height

 		# set x
 		if gridType & ROW or alignH isnt 'left' or (margin and (margin._left or margin._right) and not anchors?._autoX)


CSS HTML text measuring performance improvements

--- impl/css/level0/text.coffee
+++ impl/css/level0/text.coffee
@@ -53,10 +53,7 @@
 		# update texts sizes
 		if arr = loadingTextsByFonts[name]
 			for item in arr
-				if item._impl.containsHTML
-					updateHTMLTextSize item
-				else
-					updatePlainTextSize item
+				updateContent item
 			if implUtils.loadedFonts[name]
 				loadingTextsByFonts[name] = null
 		return
@@ -108,8 +105,8 @@
 			else
 				wordWidth += charWidth

-		item.contentWidth = width
-		item.contentHeight = height
+		data.contentWidth = width
+		data.contentHeight = height
 		return

 	updateHTMLTextSize = (item) ->
@@ -120,42 +117,55 @@
 			arr = loadingTextsByFonts[fontFamily] ||= []
 			arr.push item

-		item.contentWidth = innerElem.offsetWidth
-		item.contentHeight = innerElem.offsetHeight
+		data.contentWidth = innerElem.offsetWidth
+		data.contentHeight = innerElem.offsetHeight

 		if innerElem.parentNode is hatchery
 			implUtils.prependElement data.elem, innerElem
-		else if item._height is 0
+		else if data.contentHeight is 0
 			hatchery.appendChild data.innerElem
-			updateHTMLTextSize item
+			updateContent item
 		return

 	updateContent = do ->
 		pending = false
-		queue = []
+		currentQueue = 0
+		queues = [[], []]
+		queue = queues[currentQueue]

 		updateItem = (item) ->
 			data = item._impl
 			isAutoSize = item._autoWidth or item._autoHeight
 			if (text = data.text)
 				if data.containsHTML
-					data.innerElem.innerHTML = text
 					if isAutoSize
 						updateHTMLTextSize item
 				else
-					data.innerElem.textContent = text
 					if isAutoSize
 						updatePlainTextSize item
 			else
-				item.contentWidth = 0
-				item.contentHeight = 0
+				data.contentWidth = 0
+				data.contentHeight = 0
 			return

 		updateAll = ->
+			currentQueue = ++currentQueue % 2
+			queue = queues[currentQueue]
+
+			pending = false
+			for item in queue
+				data = item._impl
+				data.contentUpdatePending = false
+				if data.containsHTML
+					data.innerElem.innerHTML = data.text
+				else
+					data.innerElem.textContent = data.text
+			for item in queue
+				updateItem item
 			while item = queue.pop()
-				item._impl.contentUpdatePending = false
-				updateItem item
-			pending = false
+				data = item._impl
+				item.contentWidth = data.contentWidth
+				item.contentHeight = data.contentHeight
 			return

 		(item) ->
@@ -217,6 +227,8 @@
 		text: ''
 		font: "14px #{implUtils.DEFAULT_FONTS['sans-serif']}, sans-serif"
 		fontFamily: implUtils.DEFAULT_FONTS['sans-serif']
+		contentWidth: 0
+		contentHeight: 0

 	exports =
 	DATA: DATA


adding status logging

adding status logging

--- src/scripts/githooks/coreos_restart/status.coffee
+++ src/scripts/githooks/coreos_restart/status.coffee
@@ -40,7 +40,7 @@
   while true
     # Read the status of all services.
     status = yield get_status cluster, services
-
+    console.log status
     # Check for success. All services must be "active" to pass, but a single
     # failure ruins the whole thing.
     is_active = (x) -> x == "active"


Fix an error where due dates were not being shown for some items in holdings list The <div> for Items in the holding list is partially identified by call numbers. A dot in an element id is illegal syntax. Some call numbers have a dot that was not being replaced by an underscore.

--- src/opac/edit_hold.coffee
+++ src/opac/edit_hold.coffee
@@ -182,9 +182,9 @@
 		show_due_date = (x) ->
 			@append tpl_due_date {
 				barcode: x.barcode
-				#duedate: x.circulations?[0].due_date.slice 0, 10
 				duedate: datestamp x.circulations[0].due_date
 			} if x.circulations?
+			return

 		@loading 'holding details'
 		eg.openils 'search.biblio.copy_location_counts.summary.retrieve',
@@ -208,7 +208,7 @@
 					copy[status_names[id].name] = n

 				# Calculate a unique identifier for a title's holdings for an ou.
-				holding_id = ("#{copy.org_id} #{hold.target} #{copy.callnumber}").replace /\s+/g, '_'
+				holding_id = ("#{copy.org_id} #{hold.target} #{copy.callnumber}").replace /\s+|\.+/g, '_'
 				show_holding.call @, holding_id, copy

 				# For checked out copies, fill in data from circs asynchronously.


removed not used component

removed not used component

--- lib/ViewModels/SearchVueBinder.coffee
+++ lib/ViewModels/SearchVueBinder.coffee
@@ -8,10 +8,6 @@
           new Vue
             el: @view
             data: @model
-            components:
-                'my-text':
-                    props: ['text'],
-                    template: '<atom-text-editor mini /> {{ text }} </atom-text-editor>'
             methods:
                 close: () ->
                     console.log 'close'


gitlab.coffeeにミスがあったのを修正

gitlab.coffeeにミスがあったのを修正

--- gitlab.coffee
+++ gitlab.coffee
@@ -101,7 +101,7 @@
   saveInfo = (hook, url) ->

     initializeBrain hook, url
-    namespace = hook.repository.homepage.replace(gitlabUrl + "/", "")
+    namespace = hook.repository.homepage.replace(url + "/", "")

     brain[url]['user'][hook.user_id] = hook.user_name unless brain[url]['user'][hook.user_id]?
     brain[url]['repository'][hook.project_id]['namespace'] = namespace unless brain[url]['repository'][hook.project_id]['namespace']
@@ -175,9 +175,9 @@
       console.log "#{prefix}: Unknown git repository."
       return

-    if conf[git_url]['target']
+    try
       target = conf[git_url]['target']
-    else
+    catch
       try
         target = conf['DEFAULT']['target']
       catch


Add options canonicalization

Add options canonicalization

--- lib/components/options/options.coffee
+++ lib/components/options/options.coffee
@@ -31,7 +31,16 @@
         throw new Error 'Invalid sort option format: ' + msg
       return orderings

+  canonicalizeOptions: (opts) ->
+    if opts.fields?
+      if typeof opts.fields is 'string'
+        opts.fields = [opts.fields]
+      else if opts.fields not instanceof Array
+        throw new Error 'Field specification must be string or array: ' + opts.fields
+    return opts
+
   convertOptions: (opts) =>
+    opts = @canonicalizeOptions opts
     rowOpts = @rowOptions opts
     sortOpts = @sortOptions opts
     fieldOpts = @fieldOptions opts


Update lyriaScene task to use variables and new naming scheme

--- grunt/lyriaScene.coffee
+++ grunt/lyriaScene.coffee
@@ -4,7 +4,7 @@
     shortNames: true
   scenes:
     files: [{
-      dest: 'build/debug/js/scenelist.js',
+      dest: '<%= buildDebug %>/js/scenelist.js',
       src: ['assets/scenes/*'],
       filter: 'isDirectory'
     }]
@@ -14,7 +14,7 @@
       entryFile: 'prefab.js',
       markupFile: 'prefab.html'
     files: [{
-      dest: 'build/debug/js/prefablist.js',
+      dest: '<%= buildDebug %>/js/prefablist.js',
       src: ['assets/prefabs/*'],
       filter: 'isDirectory'
-    }]+    }]


add test

add test

--- spec/cson-spec.coffee
+++ spec/cson-spec.coffee
@@ -298,6 +298,7 @@
     it "calls back with an error for invalid files", ->
       callback = (error, content) ->
         expect(error).not.toBeNull()
+        expect(error.path).toEqual path.join(__dirname, 'fixtures', 'invalid.cson')
         expect(error.message).toContain path.join(__dirname, 'fixtures', 'invalid.cson')
         expect(content).toBeUndefined()


When dev mode is set, slow down infinite loop detection

--- app/lib/Angel.coco.coffee
+++ app/lib/Angel.coco.coffee
@@ -7,6 +7,7 @@
 GoalManager = require 'lib/world/GoalManager'
 {sendContactMessage} = require 'core/contact'
 errors = require 'core/errors'
+utils = require 'core/utils'

 reportedLoadErrorAlready = false

@@ -33,6 +34,14 @@
       @infiniteLoopIntervalDuration *= 10
       @infiniteLoopTimeoutDuration *= 10
       @abortTimeoutDuration *= 10
+
+    if utils.getQueryVariable('dev')
+      # Set a very long timeout so that debugging can be done without infinite loop detection
+      # breaking the debugger.
+      @infiniteLoopIntervalDuration *= 100000
+      @infiniteLoopTimeoutDuration *= 100000
+      @abortTimeoutDuration *= 100000
+
     @initialized = false
     @running = false
     @allLogs = []


notification for new coming mail

notification for new coming mail

--- app/models/mailbox.coffee
+++ app/models/mailbox.coffee
@@ -155,6 +155,17 @@
                       callback()
                   else
                     console.log "[" + results.length + "] mails to download" if debug
+
+                    mail_text = "mail"
+                    if results.length > 1
+                      mail_text = "mails"
+
+                    LogMessage.create {
+                      "type": "info",
+                      "text": "Downloading " + results.length + " " + mail_text + " from " + mailbox.name + " ",
+                      "createdAt": new Date().valueOf(),
+                      "timeout": 30
+                      }

                     mailsToGo = results.length
                     mailsDone = 0


Catch breadcrumbs for unpermitted boards

- Closes #1047

--- app/talk/breadcrumbs.cjsx
+++ app/talk/breadcrumbs.cjsx
@@ -8,6 +8,9 @@

   projectPrefix: ->
     if @props.project then 'project-' else ''
+
+  crumbCatch: (e) ->
+    ...

   render: ->
     params = @props.params
@@ -23,16 +26,16 @@
           }

         {if params.board? and not params.discussion?
-          <PromiseRenderer promise={talkClient.type('boards').get(params.board)}>{(board) =>
+          <PromiseRenderer promise={talkClient.type('boards').get(params.board)} catch={@crumbCatch}>{(board) =>
             {board.title}}
           </PromiseRenderer>}

         {if params.board? and params.discussion?
-          <PromiseRenderer promise={talkClient.type('boards').get(params.board)}>{(board) =>
+          <PromiseRenderer promise={talkClient.type('boards').get(params.board)} catch={@crumbCatch}>{(board) =>

               <Link to="#{@projectPrefix()}talk-board" params={merge({}, {board: board.id}, params)}>{board.title}</Link>
                >
-              <PromiseRenderer promise={talkClient.type('discussions').get(params.discussion)}>{(discussion) =>
+              <PromiseRenderer promise={talkClient.type('discussions').get(params.discussion)} catch={@crumbCatch}>{(discussion) =>
                 {discussion.title}}
               </PromiseRenderer>
             }


Reset to editing state once comment submitted. Closes #1972

--- app/talk/comment-box.cjsx
+++ app/talk/comment-box.cjsx
@@ -95,7 +95,7 @@

       <form className="talk-comment-form" onSubmit={@onSubmitComment}>

-        <MarkdownEditor placeholder={@props.placeholder} project={@props.project} className="full" value={@state.content} onChange={@onInputChange} onHelp={-> alert <MarkdownHelp talk={true} title={
Guide to commenting in Talk
}/> }/>
+        <MarkdownEditor previewing={@state.loading} placeholder={@props.placeholder} project={@props.project} className="full" value={@state.content} onChange={@onInputChange} onHelp={-> alert <MarkdownHelp talk={true} title={
Guide to commenting in Talk
}/> }/>

           <button
             type="button"


Show open reports as default

Show open reports as default

- Closes #1389

--- app/talk/moderations.cjsx
+++ app/talk/moderations.cjsx
@@ -24,7 +24,7 @@
     moderationsMeta: {}
     user: null
     loading: true
-    filter: null
+    filter: 'opened' # default to show open reports

   getDefaultProps: ->
     query:


datatables buttons with btn-sm

datatables buttons with btn-sm

--- test/dummy/app/assets/javascripts/myapp.js.coffee
+++ test/dummy/app/assets/javascripts/myapp.js.coffee
@@ -12,12 +12,13 @@
   # --- classic datatable ---
   table = $('table[role="datatable"], table[role="wobauth_datatable"]' ).DataTable(
     pagingType: "full_numbers"
-    dom: "<'row'<'col-md-3'l><'col-md-5 btn-group'BC><'col-md-4'f>>t<'row'<'col-md-6'ir><'col-md-6'p>>"
+    dom: "<'row'<'col-md-3'l><'col-md-5'BC><'col-md-4'f>>t<'row'<'col-md-6'ir><'col-md-6'p>>"
     stateSave: true
     lengthMenu: [ [10, 25, 100, 250, 1000], [10, 25, 100, 250, 1000] ]
     buttons: [
       {
         extend: 'copy',
+        className: 'btn-sm',
         exportOptions: {
           columns: ':visible',
           search: ':applied'
@@ -25,6 +26,7 @@
       },
       {
         extend: 'excel',
+        className: 'btn-sm',
         exportOptions: {
           columns: ':visible',
           search: ':applied'
@@ -32,6 +34,7 @@
       },
       {
         extend: 'pdf',
+        className: 'btn-sm',
         orientation: 'landscape',
         pageSize: 'A4',
         exportOptions: {
@@ -39,9 +42,13 @@
           search: ':applied'
         }
       },
-      'print',
+      {
+        extend: 'print',
+        className: 'btn-sm'
+      }
       {
         extend: 'colvis',
+        className: 'btn-sm',
         columns: ':gt(0)'
       }
     ],
@@ -76,12 +83,13 @@
       url: remotetableurl
       type: "POST"
     pagingType: "full_numbers"
-    dom: "<'row'<'col-md-3'l><'col-md-5 btn-group'BC><'col-md-4'f>>t<'row'<'col-md-6'ir><'col-md-6'p>>"
+    dom: "<'row'<'col-md-3'l><'col-md-5'BC><'col-md-4'f>>t<'row'<'col-md-6'ir><'col-md-6'p>>"
     stateSave: true
     lengthMenu: [ [10, 25, 100, 250, 1000], [10, 25, 100, 250, 1000] ]
     buttons: [
       {
         extend: 'excel'
+        className: 'btn-sm',
         title: $('table[role="remotetable"]').data('title')
         exportOptions: {
           columns: ':visible',
@@ -90,6 +98,7 @@
       },
       {
         extend: 'pdf',
+        className: 'btn-sm',
         orientation: 'landscape',
         pageSize: 'A4',
         title: $('table[role="remotetable"]').data('title')
@@ -98,9 +107,13 @@
           search: ':applied'
         }
       },
-      'print',
+      {
+        extend: 'print',
+        className: 'btn-sm'
+      }
       {
         extend: 'colvis',
+        className: 'btn-sm',
         columns: ':gt(0)'
       }
     ],


base view impl

base view impl

--- src/base_model.coffee
+++ src/base_model.coffee
@@ -222,7 +222,7 @@
   # @note Method generate add_x, where `x` it's a attribute from `@has_many` or `@has_one`
   constructor: (obj = {}) ->
     @_isValid = false
-
+    @callbacks = []
     # object, which contain all errors, which registers after validation
     @errors = {}
     @attributes = @normalize_attrs()
@@ -334,6 +334,10 @@
     throw new Error("Attribute '#{attr}' not found for #{@normal_name().toUpperCase()} model") if @attributes.indexOf(attr) == -1

     @["_#{attr}"] = value
+
+    for clb in @callbacks
+      clb.apply(null, [attr, value])
+

   #
   # Base getter
@@ -562,3 +566,85 @@
   # must be overridden in user model
   after_create: () ->

+
+  #
+  # bind
+  #
+  # 1. model to view
+  #
+  # view = Sirius.View("#span-element")
+  #
+  # model = new Model()
+  #
+  # model.bind(view, {from: 'model-attribute', to: 'view-attribute'})
+  # also `model-attribute` it might be a .errors?
+  # FIXME also need when it logical element [radio, checkbox, select]  hot to use it?
+  # <span id="span-element" data-bind-model-from: 'title', data-bind-model-to='text'>
+  # when to text, then it inner text or value for input, #FIXME when it html, then use html for insert?
+  # when it attribute need strategy
+  #
+  # 2. model to model ? # TODO thinks about it
+  #
+  # 3. model to function ?
+  #
+  bind: (view, object_setting = {}) ->
+    throw new Error("`bind` only work with Sirius.View") if !(view.name && view.name() == "View")
+
+    to = object_setting['to'] || null
+    from = object_setting['from'] || null
+
+    adapter = Sirius.Application.adapter
+    current = view.element
+
+    children = adapter.all("#{current} *")
+    count = children.length
+
+    if count == 0
+      to = adapter.get_attr(view.element, 'data-bind-view-to') || to || 'text'
+      from = adapter.get_attr(view.element, 'data-bind-view-from') || @attributes[0]
+      c to
+      c from
+      clb = (attr, value) ->
+        if attr is from
+          if to == 'text'
+            view.render(value).swap()
+          else
+            view.render(value).swap(to)
+
+      @callbacks.push(clb)
+
+    else
+      for child in children
+        do(child) ->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


Convert to the correct format on compile.

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -486,8 +486,11 @@

   return null unless lang?

-  parsed = parse code, lang, options
-  return format parsed, options
+  parsed    = parse code, lang, options
+  formatted = format parsed, options
+
+  formatted = marked formatted if options.format is "html"
+  return formatted

 #
 # @chalk function


no need to use .jade extensions on URLs

no need to use .jade extensions on URLs

--- src/client/app.coffee
+++ src/client/app.coffee
@@ -35,10 +35,10 @@
 main.config ['$routeProvider', '$locationProvider', ($routeProvider, $locationProvider) ->
   $locationProvider.html5Mode true
   $routeProvider.when '/', {
-    templateUrl: 'partials/viewWelcome.jade'
+    templateUrl: 'partials/viewWelcome'
   }
   $routeProvider.when '/character', {
-    templateUrl: 'partials/viewCharacter.jade'
+    templateUrl: 'partials/viewCharacter'
     controller: 'PriorityCreationController'
   }
   $routeProvider.otherwise redirectTo: '/'


Fix error logging.

Fix error logging.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -35,6 +35,6 @@
   logger.info message: msg
   process.exit 0
 .fail (msg) ->
-  logger.error error: error
+  logger.error error: msg
   process.exit 1
 .done()


Change default TDS version to 7.4

Change default TDS version to 7.4

--- src/connection.coffee
+++ src/connection.coffee
@@ -28,7 +28,7 @@
 DEFAULT_PACKET_SIZE = 4 * 1024
 DEFAULT_TEXTSIZE = '2147483647'
 DEFAULT_PORT = 1433
-DEFAULT_TDS_VERSION = '7_2'
+DEFAULT_TDS_VERSION = '7_4'

 class Connection extends EventEmitter
   STATE:


改进Field的Caption处理机制

改进Field的Caption处理机制

--- src/core/model.coffee
+++ src/core/model.coffee
@@ -319,7 +319,7 @@
 	retrieveData: () ->
 		cola.util.cancelDelay(@, "retrieve")

-		data = @evaluate(@, )
+		data = @evaluate(@)
 		@setTargetData(data)
 		return

@@ -1144,6 +1144,7 @@
 			else if isNaN(c)
 				@setTargetData(data)
 				return @
+
 		@parent.set(path, data, context)
 		return @


Dots: add .editorconfig.

Dots: add .editorconfig.

--- src/dots/index.coffee
+++ src/dots/index.coffee
@@ -21,6 +21,9 @@
 		},
 		{
 			name: '.travis.yml'
+		},
+		{
+			name: '.editorconfig'
 		}
 	]
 	newFiles = []


fixing bug missing callback on no work

fixing bug missing callback on no work

--- src/highlights.coffee
+++ src/highlights.coffee
@@ -137,7 +137,10 @@
     fs.readdir(dir, (err, files)=>
       if err
         return cb(err)
+
       todo = files.length
+      if !todo
+        return cb(false,[])

       while files.length
         file = files.shift()


Consolidate search routes

Consolidate search routes

--- src/scripts/router.coffee
+++ src/scripts/router.coffee
@@ -16,13 +16,7 @@
       @route 'content/:uuid(/:page)', 'media', (uuid, page) ->
         @appView.render('content', {uuid: uuid, page: page})

-      # Backbone will ignore query strings when parsing unless
-      # explicitly sent with router.navigate(), so we need
-      # two different routes to handle searches.
-      @route 'search', 'search', () ->
-        @appView.render('search')
-
-      @route /^search\?q=(.+)/, 'search', () ->
+      @route /^search/, 'search', () ->
         @appView.render('search')

     # Helper function to determine the current route's parameters


delete commented out code

delete commented out code

--- src/scripts/router.coffee
+++ src/scripts/router.coffee
@@ -8,9 +8,6 @@

   return new class Router extends Backbone.Router
     initialize: (args...) ->
-      # decodedPathname = decodeURIComponent(window.location.pathname)
-      # if (decodedPathname != window.location.pathname)
-      #   window.location.replace(decodeURIComponent(window.location))
       @appView = new AppView()

       # Default Route


访客模式下，路由不检查权限

访客模式下，路由不检查权限

--- src/initialize.coffee
+++ src/initialize.coffee
@@ -40,7 +40,7 @@
 #从session中获取member
 getMemberWithSession = (req, cb)->
   #从session中读取token
-  member_id = req.session.member_id || 0
+  member_id = req.session.member_id || -1

   #如果用户没有登录，则判断是否有记住密码
   if not member_id
@@ -124,7 +124,9 @@
         cb null, client
     #请求访问许可
     requestPermission: (client, router, action, cb)->
-      allow =  _.indexOf(router.anonymity || [], action) >= 0 || client.member.isLogin
+      allow =  _common.config.guestModel || #访客模式
+        _.indexOf(router.anonymity || [], action) >= 0 ||   #允许匿名访问
+        client.member.isLogin #用户已经登录
       cb null, allow

   _bijou.initalize(app, options)


Deprecate port.handle

Deprecate port.handle

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -32,6 +32,7 @@
       @process = process

     if options.handle
+      platform.deprecated 'InPort handle callback is deprecated. Please use Process API'
       unless typeof options.handle is 'function'
         throw new Error 'handle must be a function'
       @handle = options.handle


:lipstick:

:lipstick:

--- src/stdlib/require.coffee
+++ src/stdlib/require.coffee
@@ -36,7 +36,7 @@
   window.__filename = previousFilename
   __modules[file]

-define  = (cb) ->
+define = (cb) ->
   __defines.push ->
     exports = __modules[__filename] or {}
     module  = exports: exports


Make callback a null and branch, instead of noop.

--- src/lib/packet.coffee
+++ src/lib/packet.coffee
@@ -1,9 +1,6 @@
 parsePattern = require("./pattern").parse
 ieee754 = require "./ieee754"
 stream = require "stream"
-
-# Default callback, when no callback is provided.
-noop = (value) -> value

 # Base class for `Serializer` and `Parser`.
 class Packet extends stream.Stream
@@ -179,7 +176,7 @@
   parse: (nameOrPattern, callback) ->
     packet        = @_packets[nameOrPattern] or {}
     pattern       = packet.pattern or parsePattern(nameOrPattern)
-    callback    or= packet.callback or noop
+    callback    or= packet.callback or null

     @pattern      = pattern
     @callback     = callback
@@ -398,12 +395,13 @@
         # The pattern is set to null, our terminal condition, because the
         # callback may specify a subsequent packet to parse.
         if ++@patternIndex == @pattern.length
-          @_fields.push(this)
-          @_fields.push(p) for p in @user or []
-
           @pattern = null

-          @callback.apply @self, @_fields
+          if @callback
+            @_fields.push(this)
+            @_fields.push(p) for p in @user or []
+
+            @callback.apply @self, @_fields

         # Otherwise we proceed to the next field in the packet pattern.
         else
@@ -534,7 +532,7 @@
       ownsBuffer = true

     callback = @_reset(shiftable)
-    @callback = noop
+    @callback = null

     read = 0
     while @pattern
@@ -575,7 +573,7 @@
     else
       callback = @_reset(shiftable)
       # Implementing pause requires callbacks.
-      @callback = noop
+      @callback = null
       while @pattern
         read = @_serialize(@_buffer, 0, @_buffer.length)
         @write @_buffer.slice 0, read
@@ -596,7 +594,7 @@
           part.transforms.reverse()

     @pattern      = pattern
-    @callback     = noop
+    @callback     = null
     @patternIndex = 0

     if shiftable.length is 1 and
@@ -706,7 +704,9 @@
       # because the callback may specify a subsequent packet to parse.
       else if ++@patternIndex is @pattern.length
         @pattern = null
-        @callback.call @self, this
+
+        if @callback
+          @callback.call @self, @

       else


update play button actions

update play button actions

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -259,7 +259,7 @@
     @activateFrame(frame)

   onKeyDown: (e) =>
-    return if @playingFrames or @el.attr('flicker') is 'false' # disable while playing or in 4up
+    return if @playTimeout? or @el.attr('flicker') is 'false' # disable while playing or in 4up
     switch e.which
       when KEYS.one   then @activateFrame(0)
       when KEYS.two   then @activateFrame(1)
@@ -451,22 +451,17 @@

   onClickPlay: ->
     currentFrame = +document.getElementById('frame-slider').value
-    if @playingFrames then @stopPlayingFrames(currentFrame) else @startPlayingFrames(currentFrame)
+    if @playTimeout? then @stopPlayingFrames(currentFrame) else @startPlayingFrames(currentFrame)
     @togglePausePlayIcons()

-  stopPlayingFrames: (pauseFrame) -> # pauseFrame optional to stop on a specific frame
-    clearInterval @playingFrames
-    @enableMarkingSurfaces()
-    @playingFrames = undefined
-    @activateFrame pauseFrame ? 0
-
-  startPlayingFrames: (startingFrame) -> # startingFrame optional to start on a specific frame
-    @disableMarkingSurfaces()
-    frame = startingFrame ? 0
-    @playingFrames = setInterval (=>
-      @activateFrame frame
-      if frame is @numFrames-1 then frame = 0 else frame += 1
-    ), 500
+  startPlayingFrames: (startingFrame) ->
+    startingFrame %= @markingSurfaceList.length
+    @activateFrame startingFrame
+    @playTimeout = setTimeout (=> @startPlayingFrames startingFrame + 1), 500
+
+  stopPlayingFrames: ->
+    clearTimeout @playTimeout
+    @playTimeout = null

   togglePausePlayIcons: ->
     @el.find("#play-content").toggle()


fix bug: flicker click not recording properly

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -147,8 +147,7 @@
         @enableMarkingSurfaces()
         @currSighting = new Sighting({
           type:"asteroid",
-          inverted: @invert,
-          flicker: @el.attr 'flicker'
+          inverted: @invert
         })
         @el.find('.asteroid-classifier').show()
         @doneButton.show()


try to add preventDefault() after tap on list

--- app/controllers/users_list.coffee
+++ app/controllers/users_list.coffee
@@ -24,6 +24,7 @@

   click: (e) ->
     element = $(e.currentTarget)
+    e.preventDefault()
     item = element.data('item')
     @log 'UsersList::click ', item, item.id
     if item instanceof GmailUser


Fix Hubot brain user storage

Fix Hubot brain user storage

Modelled after the Slack Hubot implementation at:
https://github.com/slackhq/hubot-slack/blob/master/src/slack.coffee

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -50,6 +50,22 @@
         @emit 'connected'
         return true

+    userChange: (user) =>
+        return unless user?.id?
+        newUser =
+            name: user.username
+            real_name: "#{user.first_name} #{user.last_name}"
+            email_address: user.email
+            mm: {}
+        for key, value of user
+            newUser.mm[key] = value
+        if user.id of @robot.brain.data.users
+            for key, value of @robot.brain.data.users[user.id]
+                unless key of newUser
+                    newUser[key] = value
+        delete @robot.brain.data.users[user.id]
+        @robot.brain.userForId user.id, newUser
+
     loggedIn: (user) =>
         @robot.logger.info 'Logged in as user "'+user.username+'" but not connected yet.'
         @self = user
@@ -57,13 +73,14 @@
         return true

     profilesLoaded: =>
-        for key, value of @client.users
-            @robot.logger.debug 'Adding user ' +key
-            user = @robot.brain.userForId key, value
-
+        for id, user of @client.users
+            @robot.logger.debug 'Adding user '+id
+            @userChange user

     brainLoaded: =>
         @robot.logger.info 'Brain loaded'
+        for id, user of @client.users
+            @userChange user
         return true

     send: (envelope, strings...) ->


Add TODO items.

Add TODO items.

--- app/javascripts/directives.coffee
+++ app/javascripts/directives.coffee
@@ -43,6 +43,8 @@

     # Displays the MR session visit dateline chart.
+    # TODO - upgrade to new ngnvd3.
+    # TODO - rename 'visit' to 'session'.
     directives.directive 'qiVisitDateline', ['VisitDateline', '$compile',
       (VisitDateline, $compile) ->
         restrict: 'E'


Latest soil moisture data across 3 depth profiles for a single node plotted on chart

--- public/scripts/views/SoilMoistureView.coffee
+++ public/scripts/views/SoilMoistureView.coffee
@@ -1,75 +1,62 @@
 define [
+  'underscore'
   'jquery'
   'backbone'
   'amcharts'
   'amstock'
-], ($, Backbone, amcharts)-> Backbone.View.extend
+], (_, $, Backbone, amcharts)-> Backbone.View.extend
   el: '#soil-moisture-chart'

+  sensors: [
+      'field':'soil_moisture_1', 'title':'Deep'
+    ,
+      'field':'soil_moisture_2', 'title':'Surface'
+    ,
+      'field':'soil_moisture_3', 'title':'Shallow'
+  ]

-  getData: ->
-    [
-      timestamp: "2015-12-04T11:28:17.946Z"
-      temp: "8.4"
-    ,
-      timestamp: "2015-12-04T11:29:17.946Z"
-      temp: "8.0"
-    ,
-      timestamp: "2015-12-04T11:30:17.946Z"
-      temp: "8.9"
-    ]
-
-
   initialize:->
     @chart = amcharts.makeChart @el, @chartSettings()
     @listenTo @collection, 'add', @updateChart
-    # do @hardcoded

+  ###
+  Plot the moisture data for the currently selected node.  If no node has been selected then use
+  the first one that data is recieved from.
+  ###
+  updateChart: (node) ->
+    if not @nodeid?
+      @nodeid = node.id

-  hardcoded: ->
-    console.log do @getData
+    if node.id == @nodeid
+      node.observations.on 'add', (obs) =>
+        sensorType = obs.get 'sensor'
+        _.chain(@sensors).filter((f) -> obs.has(f.field) ).each (sensor) =>
+          datasetTitle = node.id + " " + sensor.title
+          dataProvider = _.findWhere(@chart.dataSets, {'title': datasetTitle})
+          if not dataProvider
+            @addToChart obs, node, datasetTitle, sensor
+          else
+            @endDate = new Date(obs.get('timestamp'))
+            dataProvider.dataProvider.push obs.toJSON()
+          do @chart.validateData
+          @chart.zoom @startDate, @endDate
+
+  ###
+  Add a dataset to the chart
+  ###
+  addToChart: (obs, node, datasetTitle, sensor) ->
+    @startDate = new Date(obs.get('timestamp'))
+    @endDate = new Date(obs.get('timestamp'))
     @chart.dataSets.push
-      dataProvider: do @getData
-      title: 'my chart'
+      dataProvider: do node.observations.toJSON
+      title: datasetTitle
       fieldMappings:
         [
-          fromField: 'temp',
+          fromField: sensor.field
           toField: 'value'
         ]
-      categoryField: "timestamp"
+      categoryField: 'timestamp'
       compared: true
-    do @chart.validateData
-
-  updateChart: (node) ->
-    node.observations.on 'add', (obs) =>
-      console.log node.id
-      if node.id == 'AF'
-        console.log do node.observations.toJSON
-        sensor = "soil_1"
-        title = node.id + " " + sensor
-        dataProvider = _.findWhere(@chart.dataSets, {'title': title})
-        if not dataProvider
-          console.log 'adding'
-          @chart.dataSets.push
-            dataProvider: do node.observations.toJSON
-            title: title
-            fieldMappings:
-              [
-                fromField: 'air_temp_1'
-                toField: 'value'
-              ]
-            categoryField: 'timestamp'
-            compared: true
-        else
-          if obs.get('air_temp_1')
-            console.log 'updating'
-            console.log obs.toJSON()
-            console.log dataProvider.dataProvider
-            dataProvider.dataProvider.push obs.toJSON()
-          else
-            console.log 'attribute not found'
-    do @chart.validateData
-

   chartSettings: ->
     categoryAxesSettings:


remove unused method

remove unused method

Signed-off-by: Steven Hoffman <46f1a0bd5592a2f9244ca321b129902a06b53e03@fustrate.com>

--- vendor/assets/javascripts/fustrate/_module.coffee
+++ vendor/assets/javascripts/fustrate/_module.coffee
@@ -118,12 +118,6 @@

     $.get "#{pathname}.json#{window.location.search}"

-  @humanDate: (date, time = false) ->
-    if date.year() is moment().year()
-      date.format("M/D#{if time then ' h:mm A' else ''}")
-    else
-      date.format("M/D/YY#{if time then ' h:mm A' else ''}")
-
   @label: (text, type) ->
     css_classes = ['label', text.replace(/\s+/g, '-'), type].compact()


Remove junk fixes for going live

Remove junk fixes for going live

--- autoform-pickadate.coffee
+++ autoform-pickadate.coffee
@@ -54,16 +54,10 @@
   $input.pickadate data.atts.pickadateOptions
   $picker = $input.pickadate('picker')

-  # After selection ensure focus is lost
-  # Fixes an issue when switching tabs and returning would open the picker
+  # After selection ensure focus is lost, fixes an issue when switching tabs
+  # and returning to the window would open the picker
   $picker.on 'close', ()->
     $(document.activeElement).blur()
-
-  $picker.on 'set', (set)=>
-    # fixes an issue where label is blank after clearing
-    if set.clear == null
-      if $($input).attr('aria-label')
-        $($input).attr('placeholder', $($input).attr('aria-label'))

   @autorun ->
     data = Template.currentData()


Changing regex

Changing regex

--- src/bgg.coffee
+++ src/bgg.coffee
@@ -17,7 +17,7 @@
 bgg = require('bgg')

 module.exports = (robot) ->
-  robot.respond /^bgg search (.*)/, (msg) ->
+  robot.respond /bgg search (.*)/, (msg) ->
     bgg('search', { query: ""})
       .then (results) ->
         console.log(results)


minor clean up fair organizer routes

minor clean up fair organizer routes

--- apps/fair_organizer/routes.coffee
+++ apps/fair_organizer/routes.coffee
@@ -7,7 +7,6 @@
 cache = require '../../lib/cache'
 OrderedSets = require '../../collections/ordered_sets'
 Articles = require '../../collections/articles'
-moment = require 'moment'

 representation = (fair) ->
   dfd = Q.defer()
@@ -38,8 +37,8 @@
   pastFairs = new Fairs

   # This grabs all the past fairs by passing fair_organizer_id
-  # to the /fairs endpoing
-  request = pastFairs.fetch
+  # to the /fairs endpoint
+  pastFairs.fetch
     data:
       fair_organizer_id: fairOrg.id
     success: (models, response, options)->


fix recursive directory bug

fix recursive directory bug

--- src/cli.coffee
+++ src/cli.coffee
@@ -16,15 +16,26 @@
     .option '-s, --sgml-line-break', 'ignore line break following a start tag and before an end tag. (for h2b mode)'
     .action (file, { mode, output, sgmlLineBreak } = {}) =>
       mode ?= 'b2h'
-      sgmlLineBreak ?= false
+      dir = output
+      removeWhiteSpace = sgmlLineBreak ? false
       throw new Error('invalid mode') if mode isnt 'b2h' and mode isnt 'h2b'
       try
         if mode is 'b2h'
-          @_compileRecursive file, dir: output
+          if fs.statSync(file).isDirectory()
+            dir ?= file
+            files = fs.readdirSync file
+            files.forEach (f) =>
+              @_compileRecursive path.join(file, f), { dir }
+          else
+            @_compile file, { dir }
         else # mode is 'h2b'
-          @_decompileRecursive file,
-            dir: output
-            removeWhiteSpace: sgmlLineBreak
+          if fs.statSync(file).isDirectory()
+            dir ?= file
+            files = fs.readdirSync file
+            files.forEach (f) =>
+              @_decompileRecursive path.join(file, f), { dir, removeWhiteSpace }
+          else
+            @_decompile file, { dir, removeWhiteSpace }
       catch e
         console.error e.message
         throw e
@@ -48,13 +59,14 @@
     dstFile = path.join dir, base + '.html'
     fs.outputFileSync dstFile, html, encoding: 'utf-8'

-  _compileRecursive: (srcFile, options) ->
+  _compileRecursive: (srcFile, { dir }) ->
     if fs.statSync(srcFile).isDirectory()
       files = fs.readdirSync srcFile
       files.forEach (f) =>
-        @_compileRecursive path.join(srcFile, f), options
+        @_compileRecursive path.join(srcFile, f),
+          dir: path.join(dir, path.basename(srcFile))
     else
-      @_compile srcFile, options
+      @_compile srcFile, { dir }

   _decompile: (srcFile, { dir, removeWhiteSpace } = {}) ->
     ext = path.extname srcFile
@@ -66,13 +78,15 @@
     dstFile = path.join dir, base + '.bhtml'
     fs.outputFileSync dstFile, bhtml, encoding: 'utf-8'

-  _decompileRecursive: (srcFile, options) ->
+  _decompileRecursive: (srcFile, { dir, removeWhiteSpace }) ->
     if fs.statSync(srcFile).isDirectory()
       files = fs.readdirSync srcFile
       files.forEach (f) =>
-        @_decompileRecursive path.join(srcFile, f), options
+        @_decompileRecursive path.join(srcFile, f),
+          dir: path.join(dir, path.basename(srcFile))
+          removeWhiteSpace: removeWhiteSpace
     else
-      @_decompile srcFile, options
+      @_decompile srcFile, { dir, removeWhiteSpace }

   _getVersion: ->
     packageJsonFile = path.join __dirname, '/../package.json'


Fix broken require.

Fix broken require.

--- src/cli.coffee
+++ src/cli.coffee
@@ -1,7 +1,7 @@
 colors = require 'colors'
 fs = require 'fs'
 path = require 'path'
-parser = '../lib/parser'
+parser = require '../lib/parser'
 pjson = require '../package.json'
 program = require 'commander'


Pull all filters left so that they are inlined

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -298,7 +298,7 @@

         @filters = _.map(@filters, (filter, name) => @createFilter(name, filter))
         filtersDiv = $(".filters", @$el)
-        _.each @filters, (filter) -> filtersDiv.append filter.render().el, " "
+        _.each @filters, (filter) -> filtersDiv.append filter.render().el
         @update()

 ###
@@ -308,7 +308,7 @@

 class Filter extends Backbone.View
     tagName: "div"
-    className: "inline"
+    className: "inline pull-left"

     initialize: ->
         @id = @options.id
@@ -327,7 +327,7 @@
     template: _.template """
         <%= name %><input type="text" class="filter <%= filterClass %>" value="<%= init %>"></input>
     """
-    className: "input-prepend inline"
+    className: "input-prepend inline pull-left"
     events:
         "change .filter": "update"

@@ -357,7 +357,7 @@
             <button class="btn <%= init == el.value ? "active" : "" %>" value="<%= el.value %>"><%= el.name %></button>
         <% }) %>
     """
-    className: "btn-group inline"
+    className: "btn-group inline pull-left"
     events:
         "click .btn": "update"


Seperate method for creating elements for strings

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -37,7 +37,10 @@
       previousAttributes = run.attributes

     # Add an extra newline if the text ends with one. Otherwise, the cursor won't move down.
-    containers.push(createContainer(string: "\n", position: @text.getLength())) if @text.endsWith("\n")
+    if @text.endsWith("\n")
+     element = createElementsForString("\n", @text.getLength())[0]
+     containers.push(element)
+
     containers

   getSelectedRange: ->
@@ -115,20 +118,28 @@
       element.style["background-color"] = "highlight" if attributes.selected

     if string
-      for substring, index in string.split("\n")
-        if index > 0
-          node = document.createElement("br")
-          node.trixPosition = position
-          position += 1
-          element.appendChild(node)
-
-        if substring.length
-          node = document.createTextNode(preserveSpaces(substring))
-          node.trixPosition = position
-          position += substring.length
-          element.appendChild(node)
+      for child in createElementsForString(string, position)
+        element.appendChild(child)

     element
+
+  createElementsForString = (string, position) ->
+    elements = []
+
+    for substring, index in string.split("\n")
+      if index > 0
+        node = document.createElement("br")
+        node.trixPosition = position
+        position += 1
+        elements.push(node)
+
+      if substring.length
+        node = document.createTextNode(preserveSpaces(substring))
+        node.trixPosition = position
+        position += substring.length
+        elements.push(node)
+
+    elements

   preserveSpaces = (string) ->
     string


Adds some stats endpoints

Adds some stats endpoints

--- src/ghe.coffee
+++ src/ghe.coffee
@@ -10,6 +10,10 @@
 #
 # Commands:
 #   hubot ghe license - returns license information
+#   hubot ghe stats issues - returns issue information
+#   hubot ghe stats hooks - returns hook information
+#   hubot ghe stats milestones - returns milestone information
+#   hubot ghe stats issues - returns user information
 #
 # Authors:
 #   pnsk, mgriffin
@@ -53,6 +57,8 @@
         results = JSON.parse body
         switch type
           when "issues" then msg.send "#{results.total_issues} issues in total; #{results.open_issues} open and #{results.closed_issues} closed."
+          when "hooks" then msg.send "#{results.total_hooks} hooks in total; #{results.active_hooks} active and #{results.inactive_hooks} inactive."
+          when "milestones" then msg.send "#{results.total_milestones} milestones in total; #{results.open_milestones} open and #{results.closed_milestones} closed."
           when "users" then msg.send "#{results.total_users} users, #{results.admin_users} admins and #{results.suspended_users} suspended."
       else
         msg.send "statusCode: #{res.statusCode} -- #{body} -- type: #{type} -- #{msg.match[1]}"


ide: sets mode when editing a file

ide: sets mode when editing a file

--- src/ide.coffee
+++ src/ide.coffee
@@ -1,6 +1,7 @@
 define (require) ->
   require("jquery.layout")
   ace = require("ace")
+  modelist = require 'ace/ext/modelist'

   template = """
   <div class="ide">
@@ -53,7 +54,10 @@

     edit: (opts) ->
       file = opts.file
+      match = modelist.getModeForPath(file.path)
+      mode = match.mode
       @ace.getSession().setValue file.content
+      @ace.getSession().setMode mode

     start: ->


Update code for md5@2.0.0

Update code for md5@2.0.0

--- src/index.cjsx
+++ src/index.cjsx
@@ -34,7 +34,7 @@
       d: @props.default
     })

-    src = base + md5.digest_s(@props.email) + "?" + query
+    src = base + md5(@props.email) + "?" + query

     return(
       <img


Update safari colors

Update safari colors

--- src/iso.coffee
+++ src/iso.coffee
@@ -330,10 +330,10 @@
   getSquareColor: (fill) ->
     color = switch fill.toLowerCase()
       when 'rgb(235, 237, 240)', '#ebedf0' then COLORS[0]
-      when 'rgb(198, 228, 139)', '#c6e48b' then COLORS[1]
-      when 'rgb(123, 201, 111)', '#7bc96f' then COLORS[2]
-      when 'rgb(35, 154, 59)',   '#239a3b' then COLORS[3]
-      when 'rgb(25, 97, 39)',    '#196127' then COLORS[4]
+      when 'rgb(155, 233, 168)', '#9BE9A8' then COLORS[1]
+      when 'rgb(50, 162, 78)', '#40C463' then COLORS[2]
+      when 'rgb(35, 154, 59)',   '#32A24E' then COLORS[3]
+      when 'rgb(33, 110, 57)',    '#216E39' then COLORS[4]
       else
         if (fill.indexOf('#') != -1)
           new obelisk.CubeColor().getByHorizontalColor(parseInt('0x'+fill.replace("#", "")));


Even bigger max bounds.

Even bigger max bounds.

--- src/map.coffee
+++ src/map.coffee
@@ -58,7 +58,7 @@
             continuusWorld: true
             worldCopyJump: false
             zoomControl: false
-            maxBounds: L.latLngBounds L.latLng(60, 24.2), L.latLng(60.5, 25.25)
+            maxBounds: L.latLngBounds L.latLng(60, 24.2), L.latLng(60.5, 25.5)
             layers: [map_layer]

         map.setView [60.171944, 24.941389], 10


skip if not active

skip if not active

--- resources/assets/lib/play-detail-menu.coffee
+++ resources/assets/lib/play-detail-menu.coffee
@@ -40,6 +40,8 @@

   hide: (e) =>
+    return if !@state.active
+
     event = e.originalEvent
     if event.keyCode == 27 || (event.button == 0 && !(@menu.current in event.composedPath()))
       @setState active: false


move ref

move ref

--- resources/assets/lib/play-detail-menu.coffee
+++ resources/assets/lib/play-detail-menu.coffee
@@ -54,6 +54,7 @@
   render: =>
     div
       className: 'play-detail-menu'
+      ref: @menu
       button
         className: 'play-detail-menu__button'
         type: 'button'
@@ -71,7 +72,6 @@
       className: "play-detail-menu__menu"
       div
         className: 'simple-menu'
-        ref: @menu
         a
           className: 'simple-menu__item'
           href: laroute.route 'users.replay',


Fix adding custom endpoints

Fix adding custom endpoints

--- app/assets/javascripts/admin/views/home/new_endpoint.js.coffee
+++ app/assets/javascripts/admin/views/home/new_endpoint.js.coffee
@@ -16,7 +16,7 @@
     @

   validation: ->
-    @$el.find('form#add-new-integration').parsley
+    @$el.find('form#add-new-endpoint').parsley
       trigger: 'change'
       listeners:
         onFormSubmit: (isFormValid, e) =>


remove console

remove console

--- app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
+++ app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
@@ -53,7 +53,7 @@
   selectAll: ->
     @setState mentorsToDisplay: _.keys @props.mentors
   onSelectDate: (mentor, day, time) ->
-    console.log @props.kid
+
     promptAndSave = ({mentorLabel, mentorKey}) =>
       if confirm """
           Treffen vereinbaren?


Play everytime currentRecording changes

Play everytime currentRecording changes

--- app/assets/javascripts/controllers/player_controller.js.coffee
+++ app/assets/javascripts/controllers/player_controller.js.coffee
@@ -13,6 +13,10 @@
     @get('currentState') == @get('states.playing')
   ).property('currentState')

+  currentRecordingDidChange: (->
+    @send('play') if @get('currentRecording') && !@get('playing')
+  ).observes('currentRecording')
+
   artistsDidChange: (->
     @set('currentRecording', null)
   ).observes('artists.@each')
@@ -22,8 +26,7 @@
   ).observes('currentState')

   play: ->
-    @set('currentRecording', @get('recordings')[0])
-    @send('play')
+    @set('currentRecording', @get('recordings.firstObject'))

   actions:
     play: -> @set('currentState', @get('states.playing'))


Commentary on Grid Layout View

Commentary on Grid Layout View

--- app/assets/javascripts/luca/components/grid_layout_view.coffee
+++ app/assets/javascripts/luca/components/grid_layout_view.coffee
@@ -4,7 +4,7 @@
 gridView.publicConfiguration
   # how many items do you wish to render per row?
   # assuming a 12 column grid, each item will receive
-  # an equal amount of columns.
+  # an equal amount of grid span/columns.
   itemsPerRow: 3

 gridView.privateConfiguration
@@ -13,6 +13,9 @@
   itemTagName: "div"

 gridView.privateMethods
+  # adds the bootstrap span class to each collection-item,
+  # where span class is equal to the 12 column grid / @itemsPerRow.
+  # for example, each item would receive a span4 class when @itemsPerRow = 3
   attributesForItem: ()->
     base = Luca.CollectionView::attributesForItem.apply(@, arguments)
     @itemsPerRow = 3 unless _.isNumber(@itemsPerRow) and @itemsPerRow > 1
@@ -21,6 +24,8 @@

     base

+  # wraps every N number of rows, where N = @itemsPerRow
+  # in a row-fluid wrapper, and appends it to the view
   renderModels: (models)->
     index = 0
     rowIndex = 0


Fix full contest achieves calculations

Fix full contest achieves calculations

--- server/calculations/calculateAchieves.coffee
+++ server/calculations/calculateAchieves.coffee
@@ -1,11 +1,12 @@
 import ACHIEVES from '../../client/lib/achieves'

 achievesConditions = (user) ->
+    chocos0 = (user.chocos?[0] || 0) - 1
     fullcontests:
-        30: 3 * user.chocos?[0] >= 30
-        15: 3 * user.chocos?[0] >= 15
-        6: 3 * user.chocos?[0] >= 6
-        3: 3 * user.chocos?[0] >= 3
+        30: 3 * chocos0 >= 30
+        15: 3 * chocos0 >= 15
+        6: 3 * chocos0 >= 6
+        3: 3 * chocos0 >= 3
     clearcontests:
         7: user.chocos?[1] >= 7
         4: user.chocos?[1] >= 4


re-use existing token in sendResetPassword method

--- server/methods/mail/sendResetPassword.coffee
+++ server/methods/mail/sendResetPassword.coffee
@@ -23,21 +23,22 @@
 		else
 			user = users[0]

-		Meteor.users.update find, $set: 'services.password.reset': token: token
+		if user.services? && user.services.password? && user.services.password.reset? && user.services.password.reset.token
+			token = user.services.password.reset.token
+		else
+			Meteor.users.update find, $set: 'services.password.reset': token: token

-		Meteor.call 'sendMail',
-			recipient: obj.email
-			sender: 'JW Management'
-			from: 'support@jwmanagement.org'
-			subject: TAPi18n.__('mail.resetPassword.subject', '', user.profile.language)
-			template: 'resetPassword'
-			language: user.profile.language
-			data:
-				token: token
+		try
+			Meteor.call 'sendMail',
+				recipient: obj.email
+				sender: 'JW Management'
+				from: 'support@jwmanagement.org'
+				subject: TAPi18n.__('mail.resetPassword.subject', '', user.profile.language)
+				template: 'resetPassword'
 				language: user.profile.language
-				content: getMailTexts 'resetPassword', user.profile.language
-		, (err, res) ->
-			if err
-				console.log 'sendMail failed: ' + err
-			else
-				true
+				data:
+					token: token
+					language: user.profile.language
+					content: getMailTexts 'resetPassword', user.profile.language
+		catch e
+			console.error e


avoid single-space hanging indent

avoid single-space hanging indent

--- src/wow.coffee
+++ src/wow.coffee
@@ -62,8 +62,8 @@

       box.setAttribute 'style', @customStyle(duration, delay, iteration)

-  customStyle: (duration, delay, iteration) ->
-    "visibility: hidden;
+  customStyle: (duration, delay, iteration) -> "
+    visibility: hidden;

     -webkit-animation-duration: #{duration};
        -moz-animation-duration: #{duration};


closing a tag on up is now optional

closing a tag on up is now optional

--- src/xml.coffee
+++ src/xml.coffee
@@ -13,8 +13,10 @@
     opts.level = @level+1
     opts = deep_merge @opts, opts # possibility to overwrite existing opts, like pretty
     @pending.push tag = new @Tag name, attrs, children, opts
-    tag.up = => # set parent
-        tag.end.apply tag, arguments
+
+    tag.up = (opts = {}) => # set parent
+        opts.end ?= true
+        tag.end.apply tag, arguments if opts.end
         this

     tag.on 'data', pipe = (data) =>


cleanup argument shifting order

cleanup argument shifting order

--- src/xml.coffee
+++ src/xml.coffee
@@ -9,7 +9,7 @@

 parse_args = (name, attrs, children, opts) ->
     unless typeof attrs is 'object'
-        [opts, children, attrs] = [children, attrs, {}]
+        [attrs, children, opts] = [{}, attrs, children]
     else
         # if attrs is an object and you want to use opts, make children null
         attrs ?= {}
@@ -349,7 +349,7 @@
                     checker(parent, tag, next)

             when 'ready', 'end'
-                [callback, tag] = [tag, parent] # shift arguments
+                [tag, callback] = [parent, tag] # shift arguments
                 next = (tag) ->
                     checker = checkers.shift() ? callback
                     checker(tag, next)


handling the exception when the text() or html() call with a non-exist element

--- src/quo.output.coffee
+++ src/quo.output.coffee
@@ -4,12 +4,12 @@
         if value or $$.toType(value) is "number"
           @each -> @textContent = value
         else
-            @[0].textContent
+            (if @length > 0 then @[0].textContent else '')

     $$.fn.html = (value) ->
         type = $$.toType(value)
         if type is "undefined"
-            @[0].innerHTML
+            (if @length > 0 then @[0].innerHTML else)
         else
             @each ->
                 if type is "string"


fix result of options call

fix result of options call

--- editor.coffee
+++ editor.coffee
@@ -314,8 +314,7 @@
         fn 'Gets or sets options', (options) ->
           if options?
             $.extend current_options, options
-          current_options
-
+          cli.object current_options

       defaults:
         cmd 'Gets or sets default options', (options) ->


Add /Courses to router

Add /Courses to router

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -66,6 +66,7 @@
     'courses/mock1/enroll/:courseID': go('courses/mock1/CourseEnrollView')
     'courses/mock1/:courseID': go('courses/mock1/CourseDetailsView')
     'courses': go('courses/CoursesView')
+    'Courses': go('courses/CoursesView')
     'courses/students': go('courses/StudentCoursesView')
     'courses/teachers': go('courses/TeacherCoursesView')
     'courses/purchase': go('courses/PurchaseCoursesView')


Round the price and profit to the penny

Round the price and profit to the penny

--- src/simulation.coffee
+++ src/simulation.coffee
@@ -63,7 +63,7 @@
         debug "Results for seller for attempt #{i}"
         debug results
         id = results.meta.reqID
-        price = results.price
+        price = Math.round(results.price * 100) / 100
         inputs = _.extend({price: price}, status)
         debug "Calling buyer for attempt #{i}"
         client.evaluate buyerID, inputs, callback
@@ -71,7 +71,7 @@
         debug "Results for buyer for attempt #{i}"
         debug results
         if results.willBuy > threshold
-          profit = price - cost
+          profit = Math.round((price - cost) * 100) / 100
         else
           profit = 0
         debug "profit for attempt #{i} = #{profit}"


Don't try to load sharing mail popover view

--- src/scripts/modules/media/title/title.coffee
+++ src/scripts/modules/media/title/title.coffee
@@ -2,7 +2,7 @@
   _ = require('underscore')
   Backbone = require('backbone')
   BaseView = require('cs!helpers/backbone/views/base')
-  MailPopoverView = require('cs!./popovers/mail/mail')
+  #MailPopoverView = require('cs!./popovers/mail/mail')
   template = require('hbs!./title-template')
   require('less!./title')

@@ -30,6 +30,6 @@
       super()
       @listenTo(@model, 'change:title change:authors change:id', @render) if @model

-    onRender: () ->
-      $share = @$el.find('.share')
-      @attachPopover new MailPopoverView({owner: $share.find('.mail'), model: @model})
+    #onRender: () ->
+    #  $share = @$el.find('.share')
+    #  @attachPopover new MailPopoverView({owner: $share.find('.mail'), model: @model})


add admin recent builds to the VM.selected

--- js/app.coffee
+++ js/app.coffee
@@ -66,7 +66,9 @@
   refreshDashboard: () =>
     VM.loadProjects()
     sel = VM.selected()
-    if sel.project_name
+    if sel.admin_builds
+      VM.loadAdminRecentBuilds()
+    else if sel.project_name
       VM.loadProject(sel.username, sel.project, sel.branch, true)
     else
       VM.loadRecentBuilds()
@@ -375,7 +377,11 @@
     @get '^/admin', (cx) -> VM.loadAdminPage cx
     @get '^/admin/users', (cx) -> VM.loadAdminPage cx, "users"
     @get '^/admin/projects', (cx) -> VM.loadAdminProjects cx)
-    @get '^/admin/recent-builds', (cx) -> VM.loadAdminRecentBuilds cx
+    @get '^/admin/recent-builds', (cx) ->
+      VM.loadAdminRecentBuilds cx
+      VM.selected
+        admin_builds: true
+
     @get '^/admin/build-state', (cx) -> VM.loadAdminBuildState cx

     # outer


Tell Mixpanel about A/B tests

Tell Mixpanel about A/B tests

--- js/libs/ab-tests.coffee
+++ js/libs/ab-tests.coffee
@@ -30,6 +30,8 @@
     @apply_overrides()

     @notify_kissmetrics()
+
+    @notify_mixpanel()

   get_user_seed: =>
     if not $.cookie(@cookie_name)?
@@ -65,3 +67,9 @@
     unpacked_tests[k] = v() for own k, v of @ab_tests()

     _kmq.push ["set", unpacked_tests]
+
+  notify_mixpanel: () =>
+    unpacked_tests = {}
+    unpacked_tests["ab_#{k}"] = v() for own k, v of @ab_tests()
+
+    mixpanel.register_once(unpacked_tests)


remove adroll call

remove adroll call

--- js/libs/tracking.coffee
+++ js/libs/tracking.coffee
@@ -26,7 +26,6 @@
     try
       $.getScript "//platform.twitter.com/oct.js", ()->
         twttr.conversion.trackPid('l4m9v')
-        __adroll.record_user adroll_segments: "payer"
     catch e
       console.error e


move IDE activation from consumed api to package activation

--- lib/integrated-learn-environment.coffee
+++ lib/integrated-learn-environment.coffee
@@ -38,6 +38,8 @@

   activate: (state) ->
     require('./init.coffee')
+    # TODO: does this need to happen everytime?
+    @activateIDE(state)

   activateIDE: (state) ->
     @oauthToken = atom.config.get('integrated-learn-environment.oauthToken')
@@ -138,8 +140,6 @@
     ipc.send 'deactivate-listener'

   consumeStatusBar: (statusBar) ->
-    # TODO: does everything in activateIDE need to run everytime a window is opened?
-    @activateIDE()
     @statusBarTile = statusBar.addRightTile(item: @fsView, priority: 5000)

   serialize: ->


Add some documentatio and blank QueryDefaults object in Adapter

--- app/assets/javascripts/embeditor/adapter.js.coffee
+++ app/assets/javascripts/embeditor/adapter.js.coffee
@@ -1,8 +1,10 @@
-# Your Adapter should define:
-# * QueryDefaults - an object of defaults to send as query parameters
+# Your Adapter SHOULD define:
+# * @QueryDefaults - The default parameters if no others are passed in.

 class Embeditor.Adapter
     className: "Adapter"
+
+    @QueryDefaults = {}

     constructor: (@element, @options={}) ->
         @adapter        = Embeditor.Adapters[@className]


fix bug in toggleAvailability

fix bug in toggleAvailability

--- both/methods/user/user.coffee
+++ both/methods/user/user.coffee
@@ -121,7 +121,7 @@
 			user.profile.available = mo: [], tu: [], we: [], th: [], fr: [], sa: [], su: []
 			Meteor.users.update Meteor.userId(), $set: 'profile.available': user.profile.available

-		if hour in user.profile.available[day]
+		if user.profile.available && hour in user.profile.available[day]
 			pull = {}
 			pull['profile.available.'+ day] = hour


fix extra commas appearing

fix extra commas appearing

--- app/pub/workout.coffee
+++ app/pub/workout.coffee
@@ -43,7 +43,7 @@
 		this._meta.map((metaSection) ->
 			'
' +
 				metaSection +
-			'
') +
+			'
').join('') +
 	'
'

 Workout = (workoutText) ->
@@ -82,8 +82,8 @@

 	html.push workouts.renderMeta()

-	html.push workouts.getAll().map (workout) ->
-		workout.render()
+	workouts.getAll().forEach (workout) ->
+		html.push workout.render()

 	this._html = html.join('')
 	this