ActivityAppController: Removed buggy docTitle logic for new activities.

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -53,22 +53,6 @@
     @status.on "reconnected", (conn)=>
       if conn?.reason is "internetDownForLongTime" then @refresh()

-    @on "activitiesCouldntBeFetched", => @listController?.hideLazyLoader()
-
-    docTitle = document.title
-    KD.getSingleton("windowController").addFocusListener (blurred) =>
-      if blurred
-        @updateDocTitle()
-      else
-        document.title = docTitle
-
-      KD.getSingleton("activityController").on "ActivitiesArrived", => @updateDocTitle()
-
-  updateDocTitle: ->
-    KD.getSingleton("appManager").tell "Activity", "getNewItemsCount", (itemCount) =>
-      if itemCount > 0
-        document.title = "(#{itemCount}) Activity"
-
   loadView:->
     @getView().feedWrapper.ready (controller)=>
       @attachEvents @getView().feedWrapper.controller


Workspace: ChatPane class is now optional.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -12,7 +12,10 @@
     @bindRemoteEvents()

   createChat: ->
-    @container.addSubView @chatView = new ChatPane delegate: this
+    chatPaneClass = @getOptions().chatPaneClass or ChatPane
+    @container.addSubView @chatView = new chatPaneClass
+      delegate: this
+
     @chatView.hide()

   createRemoteInstance: ->


Track team create result actions

Track team create result actions

--- client/landing/site.landing/coffee/team/tabs/teamusernametab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamusernametab.coffee
@@ -80,6 +80,7 @@
       KD.utils.storeNewTeamData 'username', formData
       KD.utils.createTeam
         success : (data) ->
+          track 'succeeded to create a team'
           KD.utils.clearTeamData()
           { protocol, host } = location
           location.href      = "#{protocol}//#{slug}.#{host}/-/confirm?token=#{data.token}"
@@ -88,6 +89,7 @@
             track 'requires two-factor authentication'
             @form.showTwoFactor()
           else
+            track 'failed to create a team'
             new KDNotificationView title : responseText


Update coffee to match new column name.

Update coffee to match new column name.

--- resources/assets/coffee/react/contest/voter.coffee
+++ resources/assets/coffee/react/contest/voter.coffee
@@ -54,7 +54,7 @@
     _.includes @props.selected, @props.entry.id

   render: ->
-    votingOver = moment(@props.contest.ends_at).diff() <= 0
+    votingOver = moment(@props.contest.voting_ends_at).diff() <= 0

     if (@props.selected.length >= @props.contest.max_votes || votingOver) && !@isSelected()
       null


Add keyup event listener on hour/minute inputs

 - To set the date (and trigger the onChange event) when the user is typing through its keyboard

--- app/assets/javascripts/admin/order_cycles/order_cycles.js.erb.coffee
+++ app/assets/javascripts/admin/order_cycles/order_cycles.js.erb.coffee
@@ -3,12 +3,18 @@
     require: "ngModel"
     link: (scope, element, attrs, ngModel) ->
       $timeout ->
-        flatpickr(element,  Object.assign({},
+        fp = flatpickr(element,  Object.assign({},
                             window.FLATPICKR_DATETIME_DEFAULT, {
                             onOpen: (selectedDates, dateStr, instance) ->
                               instance.setDate(ngModel.$modelValue)
                               instance.input.dispatchEvent(new Event('focus', { bubbles: true }));
                             }));
+        fp.minuteElement.addEventListener "keyup", (e) ->
+          if !isNaN(event.target.value)
+            fp.setDate(fp.selectedDates[0].setMinutes(e.target.value), true)
+        fp.hourElement.addEventListener "keyup", (e) ->
+          if !isNaN(event.target.value)
+            fp.setDate(fp.selectedDates[0].setHours(e.target.value), true)

   .directive 'ofnOnChange', ->
     (scope, element, attrs) ->


set initial waypoint when toggling flight mode on

--- app/assets/javascripts/oxalis/geometries/arbitrary_plane_info.coffee
+++ app/assets/javascripts/oxalis/geometries/arbitrary_plane_info.coffee
@@ -42,6 +42,11 @@
     value = evt.target.checked
     @model.set("flightmodeRecording", value)

+    # Set a inital waypoint when enabling flight mode
+    # TODO: use the offical wK API
+    if value = true
+      app.oxalis.arbitraryController.setWaypoint()
+

   updateCheckboxToggle : ->
     if @model.get("flightmodeRecording") == @ui.checkbox.prop("checked")


ensure progress bars do not overfill

ensure progress bars do not overfill

--- src/app/bucket-list/bucket-list.coffee
+++ src/app/bucket-list/bucket-list.coffee
@@ -38,6 +38,8 @@

         if amountDollars >= 0
           bucket.getMyContributionPercentage()
+          # ensure progress bar does not overfill
+          bucket.myContributionPercentage = Math.min(bucket.myContributionPercentage, 100 - bucket.groupContributionPercentage)

     round.getMyContributions()


nwekoding: temporarily diasable version switch menu item

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -133,14 +133,14 @@
         @hide()

-    @avatarPopupContent.addSubView new KDCustomHTMLView
-      tagName    : 'a'
-      attributes : href : '/Optout'
-      cssClass   : 'bottom-separator'
-      partial    : 'Use "old" Koding'
-      click      : (event)=>
-        Cookies.set 'useOldKoding', 'true'
-        location.reload()
+    # @avatarPopupContent.addSubView new KDCustomHTMLView
+    #   tagName    : 'a'
+    #   attributes : href : '/Optout'
+    #   cssClass   : 'bottom-separator'
+    #   partial    : 'Use "old" Koding'
+    #   click      : (event)=>
+    #     Cookies.set 'useOldKoding', 'true'
+    #     location.reload()

     # @avatarPopupContent.addSubView new KDCustomHTMLView
     #   tagName    : 'a'


managed: Added safety check on callListener

--- client/app/lib/providers/managed/managedkitechecker.coffee
+++ client/app/lib/providers/managed/managedkitechecker.coffee
@@ -68,7 +68,13 @@
    *
    * @param {...} args - The arguments to pass to the listener.
   ###
-  _callListener: (args...) -> @_getListener() args...
+  _callListener: (args...) ->
+    unless @_listeners.length
+      kd.warn "ManagedKiteChecker: _callListener called without
+        and listeners"
+      return
+
+    @_getListener() args...

   ###*


virtualization controller: use WorkflowModal

The idea is that workflow modal will add back/next buttons to the modal... the implementation still needs a lot of work, however.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -362,8 +362,8 @@
     workflow = new PaymentWorkflow
       productForm : productForm
       confirmForm : new VmPaymentConfirmForm
-
-    modal = new KDModalView
+
+    modal = new FormWorkflowModal
       title   : "Create a new VM"
       view    : workflow
       height  : "auto"


Remove dialog icon from updater atom/atom#5670

Remove the update icon on windows when showing the update modal dialog.
Keep the icon for linux. This appears to be the only file outside of
`build/` that uses the `'atom.png'` file.

--- src/browser/auto-update-manager.coffee
+++ src/browser/auto-update-manager.coffee
@@ -17,10 +17,12 @@

   constructor: (@version) ->
     @state = IdleState
+    @iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png');
     if process.platform is 'win32'
       # Squirrel for Windows can't handle query params
       # https://github.com/Squirrel/Squirrel.Windows/issues/132
       @feedUrl = 'https://atom.io/api/updates'
+      @iconPath = null;
     else
       @feedUrl = "https://atom.io/api/updates?version=#{@version}"

@@ -89,7 +91,7 @@
     dialog.showMessageBox
       type: 'info'
       buttons: ['OK']
-      icon: path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
+      icon: @iconPath
       message: 'No update available.'
       title: 'No Update Available'
       detail: "Version #{@version} is the latest version."
@@ -100,7 +102,7 @@
     dialog.showMessageBox
       type: 'warning'
       buttons: ['OK']
-      icon: path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
+      icon: @iconPath
       message: 'There was an error checking for updates.'
       title: 'Update Error'
       detail: message


Return false from isLineCommentedAtBufferRow if no line yet exists

--- src/language-mode.coffee
+++ src/language-mode.coffee
@@ -189,7 +189,7 @@
   # row is a comment.
   isLineCommentedAtBufferRow: (bufferRow) ->
     return false unless 0 <= bufferRow <= @editor.getLastBufferRow()
-    @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment()
+    @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() ? false

   # Find a row range for a 'paragraph' around specified bufferRow. A paragraph
   # is a block of text bounded by and empty line or a block of text that is not


add a minimum width to marks

add a minimum width to marks

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -97,6 +97,8 @@
     @all = []

 class Mark
+  MIN_WIDTH = 10
+
   constructor: (e, @canvasGraph) ->
     #TODO: make an active state
     @canvas = @canvasGraph.canvas
@@ -112,7 +114,7 @@
     @element.style.border =  '2px solid red'
     @element.style.borderTop =  '13px solid red'

-    @startingPoint = e.x
+    @startingPoint = e.x - MIN_WIDTH
     @dragging = false

     @element.addEventListener 'mouseover', (e) => @hovering = true
@@ -127,7 +129,7 @@
     markRightX = Math.max @startingPoint, e.x

     @element.style.left = (Math.min markLeftX, markRightX) + "px"
-    @element.style.width = (Math.abs markRightX - markLeftX) + "px"
+    @element.style.width = (Math.max (Math.abs markRightX - markLeftX), MIN_WIDTH) + "px"

     @save(markLeftX, markRightX)

@@ -168,10 +170,10 @@
     e.preventDefault()
     if (Math.abs e.layerX - (@domXMax-@domXMin)) < 10
       @startingPoint = @domXMin
-      @dragging = true
+      @resizing = true
     else if e.layerX < 10
       @startingPoint = @domXMax
-      @dragging = true
+      @resizing = true
     else if e.layerY > 15
       @moving = true
       @pointerOffset = (e.x-@domXMin)


pre-fill support link

pre-fill support link

--- src/components/navbar/server-error-monitoring.cjsx
+++ src/components/navbar/server-error-monitoring.cjsx
@@ -6,6 +6,25 @@
 {AppStore, AppActions} = require '../../flux/app'
 {CurrentUserStore, CurrentUserActions} = require '../../flux/current-user'
 Dialog = require '../tutor-dialog'
+
+SUPPORT_LINK_PARAMS = '&cu=1&fs=ContactUs&q='
+
+makeContactMessage = (statusCode, message, request) ->
+  userAgent = window.navigator.userAgent
+  location = window.location.href
+
+  errorInfo = "#{statusCode} with #{message} for #{request.method} on #{request.url}"
+
+  if request.data?
+    errorInfo += " with #{request.data}"
+
+  template = "Hello!\nI ran into a problem on #{userAgent} at #{location}.
+    Here is some additional info: #{errorInfo}."
+
+makeContactURL = (supportLinkBase, statusCode, message, request) ->
+  q = encodeURIComponent(makeContactMessage(statusCode, message, request))
+
+  "#{supportLinkBase}#{SUPPORT_LINK_PARAMS}#{q}"

 reloadOnce = ->
   navigation = AppStore.errorNavigation()
@@ -24,22 +43,26 @@
     statusCode: React.PropTypes.number.isRequired
     message: React.PropTypes.string.isRequired
     request: React.PropTypes.object.isRequired
-    supportLink: React.PropTypes.string
+    supportLinkBase: React.PropTypes.string
     debug: React.PropTypes.bool

   getDefaultProps: ->
-    supportLink: "#{CurrentUserStore.getHelpLink()}&cu=1&fs=ContactUs&q=begin"
+    supportLinkBase: CurrentUserStore.getHelpLink()
     debug: true

   render: ->
-    {statusCode, message, request, supportLink, debug} = @props
+    {statusCode, message, request, supportLinkBase, debug} = @props
+    message ?= 'No response was received'
+
+    q = makeContactMessage(statusCode, message, request)
+
     dataMessage =
       with
{request.data}

      if request.data?

     debugInfo = [

Additional error messages returned from the server is:

-
{message or 'No response was received'}

+
{message}

         {request.method} on {request.url} {dataMessage}

@@ -49,7 +72,8 @@

An error with code {statusCode} has occured

Please visit our support page to file a bug report.
+          href={makeContactURL(supportLinkBase, statusCode, message, request)}
+        >our support page to file a bug report.

         {debugInfo}

@@ -79,9 +103,9 @@
     onCancel: hideDialog

   default: (error, message, router) ->
-    if router? and not error.supportLink?
+    if router? and not error.supportLinkBase?
       {courseId} = router.getCurrentParams()
-      error.supportLink = "#{CurrentUserStore.getHelpLink(courseId)}&cu=1&fs=ContactUs&q=begin"
+      error.supportLinkBase = CurrentUserStore.getHelpLink(courseId)
     dialog:
       title: 'Server Error'
       body: <ServerErrorMessage {...error}/>


Watch: do not reload browser on every watched file change.

--- src/lib/gruntfile.coffee
+++ src/lib/gruntfile.coffee
@@ -52,7 +52,16 @@
 	if @hasSection 'watch'
 		# TODO: check duplicates
 		subsection = @toCoffee subsection, level=3
-		@gf = @gf.replace /^\t\twatch:$/m, "\t\twatch:\n\t\t\toptions:\n\t\t\t\tlivereload: true\n\t\t\t\tspawn: false\n#{subsection}"
+		@gf = @gf.replace /^		watch:$/m, """		watch:
+			options:
+				spawn: false
+			livereload:
+				options:
+					livereload: true
+				files: [
+					'build/*.{css,js}'
+				]
+#{subsection}"""
 	else
 		@addSection 'watch', subsection
 	@writeln "watcher '#{name}' added"


Update options in Gruntfiles.coffee

Update options in Gruntfiles.coffee

--- root/Gruntfile.coffee
+++ root/Gruntfile.coffee
@@ -221,7 +221,7 @@
       outRoot: '<%= path.publish %>'
       less: false
       manifest: false
-      pretty: true
+      pretty: false
       asyncScripts: false
       sharedBundles: true


Hide markers and map panel from screen readers

--- src/map-base-view.coffee
+++ src/map-base-view.coffee
@@ -98,6 +98,7 @@
             @publicTransitStopsLayer = @createPublicTransitStopsLayer()
             @publicTransitStopsLayer.addTo @map
             @postInitialize()
+            $('.leaflet-map-pane').attr('aria-hidden', true); #Hide map pane from screen reader

         createPublicTransitStopsLayer: ->
             L.markerClusterGroup
@@ -616,7 +617,7 @@
         needsSubwayIcon: -> false

         removeMarkerTabIndexes: ->
-            $('.leaflet-marker-icon').attr('tabindex', '-1'); # Set tabindex to -1 for all markers
+            $('.leaflet-marker-icon').attr('tabindex', '-1').attr('aria-hidden', true); # Set tabindex to -1 and hide from screen reader for all markers

     return MapBaseView


Use Ember.String.htmlSafe instead of deprecated…

…Ember.Handlebars.SafeString

--- addon/mixins/style-bindings.coffee
+++ addon/mixins/style-bindings.coffee
@@ -112,7 +112,7 @@
         styleTokens = styles.map (style) =>
           @createStyleString style, lookup[style]
         styleString = styleTokens.join('')
-        new Ember.Handlebars.SafeString(if (styleString.length is 0) then '' else styleString)
+        Ember.String.htmlSafe(if (styleString.length is 0) then '' else styleString)

     # add dependents to computed property
     styleComputed.property.apply(styleComputed, properties)


OEmbed: More smart charset detection algorithm

--- packages/rocketchat-oembed/server/server.coffee
+++ packages/rocketchat-oembed/server/server.coffee
@@ -9,21 +9,37 @@
 OEmbed = {}

 # Detect encoding
-getCharset = (body) ->
-	binary = body.toString 'binary'
-
-	result = jschardet.detect(binary)
-	if result.confidence > 0.8
-		return result.encoding
-
-	matches = binary.match /<meta\b[^>]*charset=["']?([\w\-]+)/i
-	if matches
-		return matches[1]
-
-	return 'utf-8'
-
-toUtf8 = (body) ->
-	return iconv.decode body, getCharset(body)
+# Priority:
+#   Detected == HTTP Header > Detected == HTML meta > HTTP Header > HTML meta > Detected > Default (utf-8)
+#   See also: https://www.w3.org/International/questions/qa-html-encoding-declarations.en#quickanswer
+getCharset = (contentType, body) ->
+	contentType = contentType || ''
+	binary = body.toString('binary')
+
+	detected = jschardet.detect(binary)
+	if detected.confidence > 0.8
+		detectedCharset = detected.encoding.toLowerCase()
+
+	m1 = contentType.match(/charset=([\w\-]+)/i)
+	if m1
+		httpHeaderCharset = m1[1].toLowerCase()
+
+	m2 = binary.match(/<meta\b[^>]*charset=["']?([\w\-]+)/i)
+	if m2
+		htmlMetaCharset = m2[1].toLowerCase()
+
+	switch detectedCharset
+		when httpHeaderCharset
+			result = httpHeaderCharset
+		when htmlMetaCharset
+			result = htmlMetaCharset
+		else
+			result = httpHeaderCharset || htmlMetaCharset || detectedCharset || 'utf-8'
+
+	return result
+
+toUtf8 = (contentType, body) ->
+	return iconv.decode(body, getCharset(contentType, body))

 getUrlContent = (urlObj, redirectCount = 5, callback) ->
 	if _.isString(urlObj)
@@ -85,7 +101,7 @@

 		callback null, {
 			headers: headers
-			body: toUtf8 buffer
+			body: toUtf8(headers['content-type'], buffer)
 			parsedUrl: parsedUrl
 			statusCode: statusCode
 		}


ForcedRegister: check if modal is shown before

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -230,8 +230,9 @@

   showRegistirationNeededModal:->
+    return if @modal
     message = "Please login to proceed to the next step"
-    modal = new KDBlockingModalView
+    @modal = modal = new KDBlockingModalView
       title           : "Koding Registration"
       content         : "
#{message}
"
       height          : "auto"


Cleaner

Cleaner

--- client/app/MainApp/fs/fsitem.coffee
+++ client/app/MainApp/fs/fsitem.coffee
@@ -13,18 +13,14 @@
         callback? err, response
         warn err
       else
-        options = switch type
-          when 'file'
-            method           : "fs.writeFile"
-            withArgs         :
-              path           : response
-              content        : ""
-              donotoverwrite : yes
-          when 'folder'
-            method           : "fs.createDirectory"
-            withArgs         :
-              path           : response
-        KD.getSingleton('kiteController').run options, (err, res)->
+        KD.getSingleton('kiteController').run
+          method           : if type is 'folder' then "fs.createDirectory" \
+                             else "fs.writeFile"
+          withArgs         :
+            path           : response
+            content        : ""
+            donotoverwrite : yes
+        , (err, res)->
           if err then warn err
           else
             file = FSHelper.createFileFromPath response, type


Fix Bug In Price Chart When There is Not Data

--- client/app/books/price-chart.coffee
+++ client/app/books/price-chart.coffee
@@ -6,7 +6,7 @@

   chartData: (->
     data = []
-    @get("content").forEach (item) ->
+    @get("content")?.forEach (item) ->
       date = item.date
       if date
         date = new Date(date)


Reload submitList data on outcome change to show good submits

--- client/components/SubmitList.coffee
+++ client/components/SubmitList.coffee
@@ -58,6 +58,19 @@
             e.preventDefault()
         @setState
             bestSubmits: not @state.bestSubmits
+
+    componentDidUpdate: (prevProps, prevState) ->
+        if not prevProps.data? or not @props.data?
+            return
+        diff = false
+        if prevProps.data.length != @props.data.length
+            diff = true
+        else
+            for i in [0...@props.data.length]
+                if prevProps.data[i].outcome != @props.data[i].outcome
+                    diff = true
+        if diff
+            @props.handleReload()

     render:  () ->
         if not @props.myUser?._id


put link to source on about page

put link to source on about page

--- client/app/scripts/components/Sobre.coffee
+++ client/app/scripts/components/Sobre.coffee
@@ -16,8 +16,9 @@
       m 'img', {src: '/img/ruc.png', style: ''}
       m 'h4', 'Tecnologias principales usadas'
       m 'p', m.trust 'CoffeeScript, Elasticsearch, Mithril, Node.js, Express, Gulp, Browserify, Pure, Fontawesome'
-      m 'h4', 'Desarrollado con Código Abierto'
-      m 'p', m.trust 'El código fuente esta disponible bajo la licencia MIT en Bitbucket, el frontend y el backend.'
+      m 'h4', 'Código Abierto'
+      m 'p', m.trust 'El código fuente esta disponible bajo la licencia MIT aquí.
+            La versión 1.1 está en Bitbucket, separado por frontend y el backend.'
       m 'p', m.trust 'Si usted quiere integrar este buscador en sus sistemas o usarlo como un webservice pongase en contacto con nosotros. '
       m 'p', m.trust ''
       m 'p', m.trust ''


Add vex to config since the brunch watcher sucks

--- SingularityUI/config.coffee
+++ SingularityUI/config.coffee
@@ -28,6 +28,7 @@
                     'vendor/scripts/backbone-1.0.0.js'
                     'vendor/scripts/moment-2.1.0.js'
                     'vendor/scripts/humanize-1.4.2.js'
+                    'vendor/scripts/vex-2.0.2.js'
                     'vendor/scripts/tether-0.1.3.js'
                     'vendor/scripts/drop-0.1.5.js'
                     'vendor/scripts/select-0.2.0.js'


targets chart: fixed height

targets chart: fixed height

--- app/assets/javascripts/d3/targets.coffee
+++ app/assets/javascripts/d3/targets.coffee
@@ -63,7 +63,7 @@

       @initialize_defaults()

-    outer_height: -> 430
+    outer_height: -> 350

     draw: =>
       margins =
@@ -73,7 +73,7 @@
         right: 20

       @width = 494 - (margins.left + margins.right)
-      @height = (@outer_height() || 402)- (margins.top + margins.bottom)
+      @height = 350 - (margins.top + margins.bottom)
       @namespace.width = @width
       t.scale.range([80, @width]) for t in @targets
       @svg = d3.select("#d3_container_targets").


Add locale to #page_body in uploader.js.coffee

Fixes so that one can upload files on pages again

--- app/assets/javascripts/uploads.js.coffee
+++ app/assets/javascripts/uploads.js.coffee
@@ -1,8 +1,8 @@
 image_exts = [".jpg", ".jpeg", ".gif", ".png", ".webp"]
 doc_exts = [".pdf", ".md", ".txt"]
-post_body = '.posts #post_body_en, .posts #post_body_sv, #page_body, #page_body'
-en_post_body = '.posts #post_body_en, #page_body'
-sv_post_body = '.posts #post_body_sv, #page_body'
+post_body = '.posts #post_body_en, .posts #post_body_sv, #page_body_sv, #page_body_en'
+en_post_body = '.posts #post_body_en, #page_body_en'
+sv_post_body = '.posts #post_body_sv, #page_body_sv'

 $ ->


Updated test to reflect new Force error handling messages

--- src/desktop/apps/user/components/delete_my_account/test/view.coffee
+++ src/desktop/apps/user/components/delete_my_account/test/view.coffee
@@ -86,11 +86,11 @@
         $.ajax
           .onCall 0
           .returns Promise.reject('There was an error')
-
+          .returns Promise.reject(responseJSON: {message: 'Sorry, this account cannot be deleted.'})
       it 'displays the error message', ->
         @view.submit $.Event()
           .then =>
             @view.$('.js-form-errors').text()
               .should.containEql 'There was an error'
             @FlashMessage.args[0][0].message
-              .should.containEql 'An error prevented us from deleting your account through this form.'
+              .should.containEql 'Sorry, this account cannot be deleted.'


Add comments + clean coffee synthax

Add comments + clean coffee synthax

--- client/app/utils/api_utils.coffee
+++ client/app/utils/api_utils.coffee
@@ -151,41 +151,42 @@

     messageClose: ->
         href = window.location.href
-        closeUrl = href.replace /\/message\/[\w-]+/gi, ''
-        closeUrl = closeUrl.replace /\/conversation\/[\w-]+\/[\w-]+/gi, ''
-        window.location.href = closeUrl
+        href = href.replace /\/message\/[\w-]+/gi, ''
+        href = href.replace /\/conversation\/[\w-]+\/[\w-]+/gi, ''
+        window.location.href = href

     messageDeleteCurrent: ->
-        if not onMessageList()
+        messageID = MessageStore.getCurrentID()
+        if not onMessageList() or not messageID?
             return
-        messageID = MessageStore.getCurrentID()
-        if not messageID?
+
+        deleteMessage = (isModal) ->
+            MessageActionCreator.delete {messageID}
+            LayoutActionCreator.hideModal() if isModal
+            Utils.messageClose()
+
+        settings = SettingsStore.get()
+
+        # Delete Message without modal
+        unless (confirm = settings.get 'messageConfirmDelete')
+            deleteMessage false
             return
-        settings = SettingsStore.get()
-        conversation = settings.get('displayConversation')
-        confirm      = settings.get('messageConfirmDelete')
-        if confirm
-            if conversation
-                confirmMessage = t 'list delete conv confirm',
-                    smart_count: 1
-            else
-                confirmMessage = t 'list delete confirm',
-                    smart_count: 1
-        if (not confirm)
-            MessageActionCreator.delete {messageID}
-            Utils.messageClose()
-        else
-            modal =
-                title       : t 'app confirm delete'
-                subtitle    : confirmMessage
-                closeLabel  : t 'app cancel'
-                actionLabel : t 'app confirm'
-                action      : ->
-                    MessageActionCreator.delete {messageID}
-                    LayoutActionCreator.hideModal()
-                    Utils.messageClose()
-            LayoutActionCreator.displayModal modal
+
+        # Display 'delete' modal
+        if (conversation = settings.get 'displayConversation')
+            confirmMessage = t 'list delete conv confirm',
+                smart_count: 1
+        else
+            confirmMessage = t 'list delete confirm',
+                smart_count: 1
+        modal =
+            title       : t 'app confirm delete'
+            subtitle    : confirmMessage
+            closeLabel  : t 'app cancel'
+            actionLabel : t 'app confirm'
+            action      : deleteMessage
+        LayoutActionCreator.displayModal modal

     messageUndo: ->


only init if we have the records

only init if we have the records

--- lineman/app/components/membership_requests_page/membership_requests_page_controller.coffee
+++ lineman/app/components/membership_requests_page/membership_requests_page_controller.coffee
@@ -1,17 +1,20 @@
-angular.module('loomioApp').controller 'MembershipRequestsPageController', ($routeParams, $rootScope, Records, LoadingService, AbilityService, FlashService) ->
+angular.module('loomioApp').controller 'MembershipRequestsPageController', ($routeParams, $rootScope, Records, LoadingService, AbilityService, FlashService, AppConfig) ->
   $rootScope.$broadcast('currentComponent', { page: 'membershipRequestsPage'})

-  @init = (group) =>
-    return if @group? or !group?
-    if AbilityService.canManageMembershipRequests(group)
-      @group = group
-      Records.membershipRequests.fetchPendingByGroup(group.key, per: 100)
-      Records.membershipRequests.fetchPreviousByGroup(group.key, per: 100)
-    else
-      $rootScope.$broadcast('pageError', {status: 403})
+  @init = =>
+    Records.groups.findOrFetchById($routeParams.key).then (group) =>
+      if AbilityService.canManageMembershipRequests(group)
+        @group = group
+        Records.membershipRequests.fetchPendingByGroup(group.key, per: 100)
+        Records.membershipRequests.fetchPreviousByGroup(group.key, per: 100)
+      else
+        $rootScope.$broadcast('pageError', {status: 403})
+    , (error) ->
+        $rootScope.$broadcast('pageError', {status: 403})

-  Records.groups.findOrFetchById($routeParams.key).then @init, (error) ->
-    $rootScope.$broadcast('pageError', error)
+
+  @init() if AppConfig.membershipsLoaded
+  $rootScope.$on('currentUserMembershipsLoaded', @init)

   @pendingRequests = =>
     @group.pendingMembershipRequests()
@@ -23,7 +26,6 @@
     Records.membershipRequests.approve(membershipRequest).then ->
       FlashService.success "membership_requests_page.messages.request_approved_success"

-
   @ignore = (membershipRequest) =>
     Records.membershipRequests.ignore(membershipRequest).then ->
       FlashService.success "membership_requests_page.messages.request_ignored_success"


"/" socket on /feed and /top (providing events for changing subscribe/recommend buttons state)

--- coffee/lib/websocket_handler.coffee
+++ coffee/lib/websocket_handler.coffee
@@ -24,7 +24,10 @@
       _.any [
         # Main
         cut == "/"
-
+        # Top
+        path == "/top"
+        # Feed
+        path == "/feed"
         # User page
         _.all [
           cut == "/u/"
@@ -44,7 +47,11 @@

     getBnwWsUrl: ->
       path = window.location.pathname
-      path = "" if path == "/"
+      path = "" if _.contains [
+          "/"
+          "/top"
+          "/feed"
+        ], path
       "#{config.BNW_WS_PROTOCOL}://#{config.BNW_WS_HOST}#{path}/ws?v=2"

     initWebSocket: ->


simplify bidid

simplify bidid

--- app/js/controllers/trollbox.coffee
+++ app/js/controllers/trollbox.coffee
@@ -14,7 +14,7 @@
   $scope.precision  = Info.PRECISION

   $scope.chatBid =
-    bidid: "#{chatAdPositionAcct}/#{chatAdPricingID}"
+    bidid: "#{chatAdPricingID}"
     asset:
       symbol: null
       price: 0


Fix #652

Fix #652

--- app/modules/vm/index.coffee
+++ app/modules/vm/index.coffee
@@ -817,7 +817,7 @@
         $scope.createVdiWaiting = false

     $scope.updateMTU = (network) ->
-      $scope.newInterfaceMTU = network.MTU
+      $scope.newInterfaceMTU = network && network.MTU

     $scope.createInterface = (network, mtu, automac, mac) ->


Remove old reload-collect stuff

Remove old reload-collect stuff

--- app/pages/lab/workflow.cjsx
+++ app/pages/lab/workflow.cjsx
@@ -21,8 +21,6 @@

   getInitialState: ->
     selectedTaskKey: @props.workflow.first_task
-    reloadCellectError: false
-    reloadCellectInProgress: false

   render: ->
     <div className="columns-container">
@@ -162,17 +160,6 @@
     else
       @props.workflow.removeLink 'subject_sets', subjectSet.id

-  reloadCellect: ->
-    reloadEndpoint = apiClient.root + ['', 'workflows', @props.workflow.id, 'reload_cellect'].join '/'
-    @setState
-      reloadCellectError: false
-      reloadCellectInProgress: true
-    apiClient.post ['', 'workflows', @props.workflow.id, 'reload_cellect'].join '/'
-      .catch (error) =>
-        @setState reloadCellectError: true
-      .then =>
-        @setState reloadCellectInProgress: false
-
   afterDelete: ->
     @props.project.uncacheLink 'workflows'
     @transitionTo 'edit-project-details', projectID: @props.project.id


Added basefolder snippet to ss3 and ss4

Added basefolder snippet to ss3 and ss4

--- completions.cson
+++ completions.cson
@@ -3062,6 +3062,25 @@
       'body': "Director::absoluteBaseURL()"
     }
   ]
+'Get base folder':
+  'prefix': 'basefolder'
+  'module': 'silverstripe/framework'
+  'variations': [
+    {
+      'scope': '.text.html.php'
+      'minVersion': '4.0'
+      'body': "Director::baseFolder()"
+      'useNamespace': [
+        'SilverStripe\\Control\\Director'
+      ]
+    }
+    {
+      'scope': '.text.html.php'
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+      'body': "Director::baseFolder()"
+    }
+  ]

 # General variables
 'Current Member':


Fix order_stash plugin animations for safari browser

--- src/plugins/order_stash.coffee
+++ src/plugins/order_stash.coffee
@@ -63,6 +63,7 @@
     border-radius: 2px 2px 0 0;
     z-index: 2147483647;

+    -webkit-animation: sa-order-stash-slide-in-#{configuration.position.split('-')[1]} 1s;
     animation: sa-order-stash-slide-in-#{configuration.position.split('-')[1]} 1s;
   }

@@ -71,6 +72,7 @@
   }

   #sa-order-stash-plugin.sa-order-stash-slide-out {
+    -webkit-animation: sa-order-stash-slide-out-#{configuration.position.split('-')[1]} .5s;
     animation: sa-order-stash-slide-out-#{configuration.position.split('-')[1]} .5s;
     animation-fill-mode: forwards;
   }


Changed Volunteers to Registered Volunteers on Stats Page

The stats page quotes the number of project volunteers but it’s only
the registered volunteers. Adding in Registered to match the Landing
page statistics format

--- app/pages/project/stats/stats.cjsx
+++ app/pages/project/stats/stats.cjsx
@@ -194,7 +194,7 @@
           {start}

{@props.totalVolunteers.toLocaleString()}

-
Volunteers

+
Registered Volunteers

{@props.currentClassifications.toLocaleString()}


move init code down

move init code down

--- app/scripts/notebook.coffee
+++ app/scripts/notebook.coffee
@@ -102,17 +102,6 @@
         # let CodeMirror handle this
         CodeMirror.Pass

-makeInputCell = (inputCell) ->
-  editor = CodeMirror.fromTextArea inputCell,
-    mode: "coffeescript"
-  inputCell.editor = editor
-  addKeyMap editor, inputCell
-  editor
-
-
-for i in [1, 2, 3]
-  input = document.getElementById("input_#{i}")
-  makeInputCell input

 $(document).bind 'keydown', 'down', (ev) -> moveCellCursorDown()
 $(document).bind 'keydown', 'up', (ev) -> moveCellCursorUp()
@@ -138,3 +127,16 @@
       insertCellCursorAfter($(cells[0]))
     else
       insertCellCursorBefore($(cells[0]))
+
+
+# Initialize the example notebook
+# -------------------------------
+
+makeInputCell = (inputCell) ->
+  editor = CodeMirror.fromTextArea inputCell,
+    mode: "coffeescript"
+  inputCell.editor = editor
+  addKeyMap editor, inputCell
+  editor
+
+makeInputCell(c) for c in $(".cell.input > textarea")


freePositions on ConnectFour

freePositions on ConnectFour

--- app/src/connect-four.coffee
+++ app/src/connect-four.coffee
@@ -72,7 +72,7 @@
   isTerminal: -> isTerminal @a
   nextAgent: -> if @nextPlayer is X then MAX else MIN
   utility: -> evaluate @a
-  openPositions: -> openPositions @a
+  freePositions: -> freePositions @a
   possibleActions: -> openColumns @a
   play: (columnIndex) ->
     new @constructor (play @a, columnIndex, @nextPlayer), @opponent(), @depth + 1


talkを追加 かえる/帰る、おやすみ

talkを追加
かえる/帰る、おやすみ

--- scripts/idobata-talk.coffee
+++ scripts/idobata-talk.coffee
@@ -47,6 +47,10 @@
   robot.hear /おはよー/i, (msg) ->
     msg.send "おはよーじゃなくて、おはようございますでしょっ"

+  robot.hear /かえる|帰る/i, (msg) ->
+    msg.send "はぁはぁはぁ…。き、奇遇ね、わ、私も今ちょうど帰るとこなんだけど、ぜぇぜぇ。"
+
+
   # respond
   robot.respond /嫌い？$/i, (msg) ->
     words = [
@@ -86,4 +90,11 @@
     msg.send msg.random [
       "ふ、二人しかいない時なら…って調子に乗るなーっ"
       "しょうがないわね。ほら、一分だけよ？"
-    ]+    ]
+
+  robot.respond /おやすみ/i, (msg) ->
+    msg.send msg.random [
+      "私も眠くなってきたわ・・・。 (つ∀-)ｵﾔｽﾐｰ"
+      "まだ仕事残ってるんじゃないの？ しょうがないから終わるまで付き合ってあげるわ… 居眠りなんてさせないんだから///"
+    ]
+


fix hash scrolling

fix hash scrolling

--- documentation/app/static/scripts/modules/process-documentation.coffee
+++ documentation/app/static/scripts/modules/process-documentation.coffee
@@ -50,16 +50,16 @@
           $('body').scrollspy 'refresh'
           $(@elements.nav).find('a[href="'+hash+'"]').parent().addClass 'active'

+      @scrollToHash(hash, 20)
+
       if imgLength is 0
         initOrRefresh()
-        @scrollToHash(hash)
       else
         $imgs.on 'load', =>
           imgLoaded++

           if imgLoaded is imgLength
             initOrRefresh()
-            @scrollToHash(hash, 30)

     processHtml: ->
       $('pre').addClass 'prettyprint'


Better error messages

Better error messages

--- tasks/spec-task.coffee
+++ tasks/spec-task.coffee
@@ -2,6 +2,7 @@
 path = require 'path'

 _ = require 'underscore-plus'
+
 async = require 'async'

 module.exports = (grunt) ->
@@ -10,7 +11,7 @@
   packageSpecQueue = null

   runPackageSpecs = (callback) ->
-    passed = true
+    failedPackages = []
     rootDir = grunt.config.get('atom.shellAppDir')
     appDir = grunt.config.get('atom.appDir')
     atomPath = path.join(appDir, 'atom.sh')
@@ -25,9 +26,8 @@
           env: _.extend({}, process.env, ATOM_PATH: rootDir)
       grunt.verbose.writeln "Launching #{path.basename(packagePath)} specs."
       spawn options, (error, results, code) ->
-        if error
-          passed = false
-          grunt.log.error "#{path.basename(packagePath)} specs failed"
+
+        failedPackages.push path.basename(packagePath) if error
         callback()

     modulesDirectory = path.resolve('node_modules')
@@ -38,7 +38,7 @@
       packageSpecQueue.push(packagePath)

     packageSpecQueue.concurrency = 1
-    packageSpecQueue.drain = -> callback(null, passed)
+    packageSpecQueue.drain = -> callback(null, failedPackages)

   runCoreSpecs = (callback) ->
     contentsDir = grunt.config.get('atom.contentsDir')
@@ -51,14 +51,19 @@
       args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
     spawn options, (error, results, code) ->
       packageSpecQueue.concurrency = 2
-      callback(null, not error)
+      callback(null, error)

   grunt.registerTask 'run-specs', 'Run the specs', ->
     done = @async()
     startTime = Date.now()

     async.parallel [runCoreSpecs, runPackageSpecs], (error, results) ->
-      [coreSpecPassed, packageSpecsPassed] = results
+      [coreSpecFailed, failedPackages] = results
       elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
       grunt.verbose.writeln("Total spec time: #{elapsedTime}s")
-      done(coreSpecPassed and packageSpecsPassed)
+      failures = failedPackages
+      failures.push "atom core" if coreSpecFailed
+
+      grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0
+
+      done(!coreSpecFailed and failedPackages.length == 0)


tiny improvements

tiny improvements

--- private/js/app.coffee
+++ private/js/app.coffee
@@ -8,7 +8,7 @@
   $overlay.on 'click', ->
     $overlay
       .css visibility: 'hidden'
-      .children().remove().end()
+      .children().remove()

   $('form').on 'submit', (e) ->
     last = $overlay.data('params')
@@ -28,7 +28,7 @@
       .text hits
       .data {hits}

-  $('input[type=text], input[type=number]').on 'focus', ->
+  $('input.text').on 'focus', ->
     $(@).select()

   $('input[type=range]').on 'change', (e) ->


Use default value if incoming IP isn't a string

--- components/TemplateReplace.coffee
+++ components/TemplateReplace.coffee
@@ -8,13 +8,18 @@

   constructor: ->
     @template = null
+    @default = ''

     @inPorts =
       in: new noflo.Port()
       template: new noflo.Port()
       token: new noflo.Port()
+      # Default value for non-string input
+      default: new noflo.Port()
     @outPorts =
       out: new noflo.Port()
+
+    @inPorts.default.on "data", (@default) =>

     @inPorts.template.on "data", (template) =>
       @template = template if _.isString template
@@ -53,7 +58,8 @@
       # Also accept a series of IPs
       token = @tokens[@tokenPos++]
       pattern = new RegExp token, 'g'
-      @output = @output.replace pattern, data
+      replacement = if _.isString data then data else @default
+      @output = @output.replace pattern, replacement

     @inPorts.in.on "endgroup", =>
       @outPorts.out.endGroup()


update clock view test

update clock view test

--- components/clock/test/view.coffee
+++ components/clock/test/view.coffee
@@ -54,7 +54,7 @@
       @view.$el.html '

'
       @view.render()
       @view.$el.html().should.containEql 'days'
-      @view.$el.html().should.containEql 'months'
+      @view.$el.html().should.containEql 'mos'
       @view.$el.html().should.not.containEql '00'

     it 'excludes months sectoin if sale starts 0 months from now', ->


get rid of some globals

get rid of some globals

--- app/vis.coffee
+++ app/vis.coffee
@@ -8,7 +8,6 @@
 module.exports = class Main
     SCALE_TEXT = 0.005
     RADIUS = 700
-    CAMERA_Z = -9

     SPEED_MULTIPLIER = 0.5

@@ -33,8 +32,8 @@
         [ near, far ] = [ 0.1, 1000 ]
         @camera = new THREE.PerspectiveCamera( fov, aspect, near, far )

-        [_x,_y] = [0,0]
-        @camera.position.z = CAMERA_Z
+        [_x, _y, _z] = [0, 0, -9]
+        @camera.position.z = _z
         @camera.position.x = _x
         @camera.position.y = _y
         @camera.lookAt new THREE.Vector3(_x,_y,0)
@@ -177,7 +176,7 @@

         network.reduceRight(_makeTree)

-    setNetworkPositions = (root) =>
+    setNetworkPositions = (root, radius) =>
         root.position = new THREE.Vector3()

         radianScale = d3.scale.ordinal()
@@ -196,8 +195,8 @@
             circle_nodes.forEach (circle_node, index) ->
                 radians = radianScale(index) + offset
                 pos = new THREE.Vector3(
-                    RADIUS * Math.cos(radians) + node.position.x,
-                    RADIUS * Math.sin(radians) + node.position.y
+                    radius * Math.cos(radians) + node.position.x,
+                    radius * Math.sin(radians) + node.position.y
                 )
                 if circle_node.position?
                     # check = pos.y.toFixed(3) is circle_node.position.y.toFixed(3)
@@ -298,8 +297,10 @@
         network_object.scale.multiplyScalar(SCALE_TEXT)
         @scene.add network_object

+        radius = 500
+
         root = makeTree network
-        root = setNetworkPositions root
+        root = setNetworkPositions root, radius

         traverse = (node) =>
             return if ! node.position?


lots of stuff; basic lines mode transitions

--- app/vis.coffee
+++ app/vis.coffee
@@ -218,6 +218,11 @@
                         @camera.position.x = x(t)
                         @camera.position.y = y(t)
                 .each "end", resolve
+
+    panCameraToBBox: (object, duration) =>
+        bbox = new THREE.BoundingBoxHelper( object, 0xff0000 )
+        do bbox.update
+        @panCameraToPosition bbox.box.center(), 1000

     zoomCameraToPosition: (target, duration) =>
         new Promise (resolve) =>
@@ -244,17 +249,22 @@
                         @camera.position.y = y(t)
                 .each "end", resolve

-    getTransitionPromise = (interpolator) ->
+    getTransitionPromise = (interpolator, duration) ->
         (text_object) ->
             new Promise (resolve) ->
                 d3.select(text_object).transition()
-                    .duration FADE_DURATION
+                    .duration duration or FADE_DURATION
                     .ease "poly", 5
                     .tween "fadeOpacity", ->
                         i = interpolator
                         (t) -> this.children.forEach (mesh) ->
                             mesh.material.uniforms.opacity.value = i(t)
                     .each "end", resolve
+
+    fade = (from, to, duration) ->
+        (text_object) ->
+            i = d3.interpolate from, to
+            getTransitionPromise(i, duration)(text_object)

     fadeOut = (text_object) ->
         i = d3.interpolate(0.5, 0)
@@ -346,6 +356,7 @@

     alignToNode = (parent) ->
         (child) ->
+            parent_x = parent._text_object.position.x
             word = parent.word
             regex = new RegExp word, "i"
             offset = [ parent, child ]
@@ -353,7 +364,35 @@
                     idx = _.line.search regex
                     _._text_object.children[idx].position.x
                 .reduce (a, b) -> a - b
-            child._text_object.position.x += offset
+            child._text_object.position.x = parent_x + offset
+
+    addObjects: (lines_object) =>
+        (root) =>
+            traverse = (node) =>
+                debugger if ! node.line?
+                node._text_object = @getLineObject(node.line)
+                node._text_object.children.forEach (mesh) ->
+                    mesh.material.uniforms.opacity.value = 0.2
+                lines_object.add node._text_object
+
+                node.children.forEach traverse if node.children
+
+            traverse(root)
+
+    # transitionTargetWord: (word, transition) =>
+    #     (child) =>
+    #
+
+    animateLines: (root) =>
+        traverse = (node) =>
+            @panCameraToBBox node._text_object
+                .then -> fade(0.2, 1, 1000) node._text_object
+                .then ->
+                    if node.children?
+                        promises = node.children.map (child) ->
+                             fade(0.2, 1, 1000) child._text_object
+
+        traverse root

     addLines: (lines) =>
         lines_object = new THREE.Object3D()
@@ -363,28 +402,20 @@

         root = linesToTree lines

+        @addObjects(lines_object)(root)
+
         traverse = (parent) =>
-            parent._text_object ?= @getLineObject(parent.line)
-            # parent._text_object.children.forEach (mesh) ->
-            #     mesh.material.uniforms.opacity.value = 0.5
-            lines_object.add parent._text_object
-
             return if ! parent.children?
-
-            parent.children.forEach (child) =>
-                child._text_object ?= @getLineObject(child.line)
-                child._text_object.children.forEach (mesh) ->
-                    mesh.material.uniforms.opacity.value = 0.5
-                lines_object.add child._text_object

             positions_array = d3.shuffle parent.children.concat parent
             parent_index = positions_array.indexOf parent
+            parent_y = parent._text_object.position.y

             positions_array.forEach (node, index) =>
                 offset_from_parent = index - parent_index
                 obj = node._text_object
-                height = obj._layout.height
-                obj.position.y = offset_from_parent * (height + LINE_SPACING)
+                line_height = obj._layout.height + LINE_SPACING
+                obj.position.y = parent_y + offset_from_parent * line_height

             parent.children.forEach alignToNode(parent)

@@ -392,25 +423,34 @@

         traverse root

-        # root._text_object ?= @getLineObject(root.line)
-        # root._text_object.children.forEach (mesh) ->
-        #     mesh.material.uniforms.opacity.value = 1
-        # lines_object.add root._text_object
-        #
-        # bbox = new THREE.BoundingBoxHelper( root._text_object, 0xff0000 )
-        # do bbox.update
-        #
-        # @scene.add bbox
-        #
-        # @panCameraToPosition bbox.box.center(), 1000
-        #     .then => @zoomToBoundingBoxWidth(bbox.box, 1000)
-
+        # addBBox = (node) =>
+        #     bbox = new THREE.BoundingBoxHelper( node._text_object, 0xff0000 )
+        #     do bbox.update
+        #     @scene.add bbox
+        #     if node.children
+        #         node.children.forEach addBBox
+        # addBBox root
+
+        @animateLines root

     animate: =>
         requestAnimationFrame @animate
         @renderer.render( @scene, @camera )

 module.exports = Main
+
+# bbox = new THREE.BoundingBoxHelper( root._text_object, 0xff0000 )
+# do bbox.update
+#
+# @scene.add bbox
+
+# bbox = new THREE.BoundingBoxHelper( root._text_object, 0xff0000 )
+# do bbox.update
+#
+# @scene.add bbox
+#
+# @panCameraToPosition bbox.box.center(), 1000
+#     .then => @zoomToBoundingBoxWidth(bbox.box, 1000)

 # app = createOrbitViewer({
 #     clearColor: 'rgb(255, 255, 255)',


fix foreign scope issue

fix foreign scope issue

--- lib/ServiceResponse.coffee
+++ lib/ServiceResponse.coffee
@@ -10,7 +10,7 @@

   valid: false

-  getMessage: (args, err) ->
+  getMessage: (args, err) =>
     JSON.stringify
       id: @req.id
       service: @req.service
@@ -21,32 +21,32 @@
         type: err.type
         stack: err.stack

-  getAnonymousMessage: (args) ->
+  getAnonymousMessage: (args) =>
     JSON.stringify
       service: @req.service
       args: args if args?

-  publish: (args...) ->
+  publish: (args...) =>
     socket.send @getAnonymousMessage args for id, socket of @socket.server.clients
     return @

-  send: (args...) ->
+  send: (args...) =>
     @socket.send @getMessage args
     return @

-  cookie: (key, val) ->
+  cookie: (key, val) =>
     return @req.cookies unless key or val
     if key and not val
       return @req.cookies[key]
     else
       @req.cookies[key] = val

-  error: (err) ->
+  error: (err) =>
     err = new Error err unless err instanceof Error
     @socket.send @getMessage null, err
     return @

-  close: ->
+  close: =>
     @socket.close()
     return @


removed the original google coffescript

removed the original google coffescript

--- scripts/google.coffee
+++ scripts/google.coffee
@@ -1,15 +0,0 @@
-# Returns the URL of the first google hit for a query
-#
-# google me <query>   - Googles <query> & returns 1st result's URL
-
-module.exports = (robot) ->
-  robot.respond /(google)( me)? (.*)/i, (msg) ->
-    googleMe msg, msg.match[3], (url) ->
-      msg.send url
-
-googleMe = (msg, query, cb) ->
-  msg.http('http://www.google.com/search')
-    .query(q: query)
-    .get() (err, res, body) ->
-      cb body.match(/<a href="([^"]*)" class=l>/)?[1] || "Sorry, Google had zero results for '#{query}'"
-


Switch parser by From header

Switch parser by From header

--- scripts/mailin.coffee
+++ scripts/mailin.coffee
@@ -22,12 +22,11 @@
       res.end "NG"
       return

-    messageId = data.headers['Message-ID']
-    host = messageId.replace(/^<|>$/g, "").split(/@/)[1]
-    console.log data.envelope
+    from = data.headers.From.match(/<([^>]+)>$/)[1]
+    console.log "from: #{from}"

-    switch host
-      when "disqus.net"
+    switch from
+      when "notifications@disqus.net"
         robot.logger.info "Disqus mail parser catched"
         parser = new DisqusMailParser data
         # for Slack


Continue interval without point increment to save reset.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -98,22 +98,22 @@
     #
     #     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) ->
-    #         chat = JSON.parse(body)
-    #         people = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers]).filter((p) ->
-    #             p != 'awebot'
-    #         )
-    #
-    #         for username in people
-    #             points[username] ?= 0
-    #             points[username] += 5
-    #             save(robot)
-    #
-    #         # Winners (and then recall) is equal to all users
-    #         # with points, and their points respectively.
-    #         robot.brain.set 'winners', points
-    # ), 60000
+    setInterval (->
+        robot.http("https://tmi.twitch.tv/group/user/masonest/chatters").get() (err, res, body) ->
+            chat = JSON.parse(body)
+            people = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers]).filter((p) ->
+                p != 'awebot'
+            )
+
+            for username in people
+                points[username] ?= 0
+                # points[username] += 5
+                save(robot)
+
+            # Winners (and then recall) is equal to all users
+            # with points, and their points respectively.
+            robot.brain.set 'winners', points
+    ), 60000

     # Get a list of stored people and points
     robot.respond /getset$/i, (msg) ->


remove old code, a bit of clean up

remove old code, a bit of clean up

--- src/scripts/drush.coffee
+++ src/scripts/drush.coffee
@@ -9,7 +9,7 @@

 drush_interface = ->
   site_aliases = []
-  allowed_commands = ["sa", "cc", "pml", "unif", "pmi", "ws", "vget", "rq"]
+  #allowed_commands = ["sa", "cc", "pml", "unif", "pmi", "ws", "vget", "rq"]

   # helper method to propigate the site aliases in memory
   update_aliases = (msg) ->
@@ -30,9 +30,7 @@
   # run the update script upon construction
   update_aliases()

-  # Our allowed commands
-  commands =
-    # site aliases, the only command that does not need an alias prefix
+  allowed_commands =
     drush_sa: (msg, command) ->
       if command.args.indexOf('--update-aliases') is -1
         msg.send "If this list is empty or has unexpected results update aliases ('drush sa --update-aliases')."
@@ -43,6 +41,7 @@

     drush_cc: (msg, command) ->
       output = ''
+      msg.send "This may take a minute..."
       clearcache = spawn("drush", [command.alias, "cc", "all"])
       clearcache.stdout.on "data", (data) ->
         output += data
@@ -53,12 +52,12 @@
           output += "Cache clear complete."
         msg.send output

-    drush_pml: (msg, command) ->
-      output = ''
-      # @TODO loop through command.args and append args that appear in allowed_options
-      allowed_options = ['--enabled', '--disabled', '--core', '--no-core']
-      spawn_options = [command.alias, "pml", "--pipe"]
-      pml = spawn("drush", [command.alias, "pml", "--status=enabled", "--no-core"])
+#   drush_pml: (msg, command) ->
+#     output = ''
+#     # @TODO loop through command.args and append args that appear in allowed_options
+#     allowed_options = ['--enabled', '--disabled', '--core', '--no-core']
+#     spawn_options = [command.alias, "pml", "--pipe"]
+#     pml = spawn("drush", [command.alias, "pml", "--status=enabled", "--no-core"])

   # parsing the user input after "drush "
   parse_command = (user_command) ->
@@ -71,13 +70,12 @@
       command_suff = site_alias
       site_alias = ''
     command = "drush_" + command_suff
-    if typeof commands[command] is "function"
+    if typeof allowed_commands[command] is "function"
       return (
         cmnd: command
         alias: site_alias
         args: extra_args
       )
-      command
     else
       `undefined`

@@ -87,7 +85,7 @@
   execute: (msg) ->
     command = parse_command(msg.match[1])
     unless command is `undefined`
-      commands[command.cmnd](msg, command)
+      allowed_commands[command.cmnd](msg, command)
     else
       msg.send "'drush " + msg.match[1] + "' is and invalid command. Please try again."

@@ -99,47 +97,3 @@
   robot.respond /drush (.*)$/i, (msg) ->
     msg.send "drush fired!"
     drush.execute(msg)
-
-#module.exports = (robot) ->
-#  robot.respond /drush sa$/i, (msg) ->
-#    #    if not process.env.DRUSH_UID?
-#    # msg.send "DRUSH_UID is not set. Cannot complete this action."
-#    #  return
-#
-#    sitelist = 'Site Aliases Available: \n'
-#    sitealias = spawn("drush", ["sa"])
-#    sitealias.stdout.on "data", (data) ->
-#      sitelist = sitelist + data
-#
-#    sitealias.on "exit", (code) ->
-#      msg.send sitelist
-#      msg.send 'sa command complete.'
-#
-#  robot.respond /drush cc (.*)$/i, (msg) ->
-#    ccout = ''
-#    clearcache = spawn("drush", [msg.match[1], "cc", "all"])
-#    msg.send "This may take a minute, please be patient..."
-#    clearcache.stdout.on "data", (data) ->
-#      ccout = ccout + data
-#
-#    clearcache.stderr.on "data", (data) ->
-#      ccout = ccout + data
-#
-#    clearcache.on "exit", (code) ->
-#      if code is 0
-#        msg.send ccout
-#        msg.send "--== command complete ==--"
-#      else
-#        msg.send "Drush experienced and error."
-#
-#  robot.respond /drush pml (.*)$/i, (msg) ->
-#    pmlOut = '\n'
-#    pml = spawn("drush", [msg.match[1], "pml", "--status=enabled", "--no-core"])
-#    pml.stdout.on "data", (data) ->
-#      pmlOut = pmlOut + data
-#    pml.stderr.on "data", (data) ->
-#      pmlOut = pmlOut + data
-#    pml.on "exit", (code) ->
-#      msg.send pmlOut
-#      msg.send "--== command complete ==--"
-


Change descriptions

Change descriptions

--- scripts/yamada.coffee
+++ scripts/yamada.coffee
@@ -81,9 +81,10 @@
        json = JSON.parse body
        weather = json['forecasts'][0]['telop']
        max_temperature =  json['forecasts'][0]['temperature']['max']['celsius']
-       msg.send "今日の天気は#{weather}ってとこだな。最高気温は#{max_temperature}度らしいよ"
+       msg.send "今日の天気は#{weather}ってとこだな。最高気温は#{max_temperature}度らしいよ。"

    robot.respond /yahoo-news/i, (msg) ->
+     msg.send "今のYahoo Newsな。詳細は自分で確認して。"
      cheerio-httpcli.fetch 'http://www.yahoo.co.jp/', {}, (err, $, res)->
        $('ul.emphasis > li > a').each ()->
-         msg.send "#{$(this).text()}"
+         msg.send "・#{$(this).text()}"


maybe finished, but problems with the hypothetical, so can't really test

--- basicAI.coffee
+++ basicAI.coffee
@@ -413,39 +413,55 @@
     #    Then, ignore as many terminals as you can play this turn, return the others
     #
     else
-      # take terminals from hand
-      # and sort them by actionPriority (highest first)
-      # just take the once you can not play by the actions you have
       putBack = (card for card in my.hand when (card?.isAction and card?.getActions(state)==0))
       putBack = putBack.sort( (y, x) -> my.ai.choiceToValue('action', state, x) - my.ai.choiceToValue('action', state, y) )
       putBack = putBack[my.countPlayableTerminals(state) ... putBack.length]

     # 3) Put back as much money as you can
-    #    First get a pessimistic estimate of the avaiable money this turn
-    #    then find take the cost in coins of the highest priority card you can afford
-    #    then put the treasures which are not needed in 'putBack' for discardPriority, most valuable first, Potion = 2.5 Coins
-    #
-    state.log("PutBack: #{putBack}")
-    if putBack.length==0
-      coinEstimate = my.ai.pessimisticMoneyInHand(state)
-      potionEstimate = my.countInHand(state.cardInfo["Potion"])
+    if putBack.length == 0
+      # find out what you would gain if you put back worst card as in 4)
+      [hypState, hypMy] = state.hypothetical(my.ai)
+      # technically, we don't want to discard but to put back, but for pessimisticBuy this should not matter
+      dis = hypState.requireDiscard(hypMy, 1)
+      state.log("Discarded: #{dis}")
+      defaultGained = hypMy.ai.pessimisticCardsGained(hypState)
+      state.log("Defaultgain: #{defaultGained}")

-      targets = (state.cardInfo[card]?.getCost(state) for card in my.ai.gainPriority(state, my) when ( (state.cardInfo[card]?.getCost(state)[0] <= coinEstimate) and (state.cardInfo[card]?.getCost(state)[1] <= potionEstimate )) )
+      # determine which treasures are in hand, discard them hypothetically and test if the gains change.  When it doesn't, we can put back this card.
+      treasures = []

-      if targets? and targets.length? and targets.length>0
-        coinTarget   = targets[0][0]
-        potionTarget = targets[0][1]
-      else
-        coinTarget   = 0
-        potionTarget = 1
+      for card in my.hand
+        if (card.isTreasure) and (not (card in treasures))
+          treasures.push card
+
+      for card in treasures
+        [hypState, hypMy] = state.hypothetical(my.ai)
+        hypMy.doDiscard(card)
+        nowGained = hypMy.ai.pessimisticCardsGained(hypState)
+        state.log("card #{card}, HypGaines: #{nowGained}")
+        # test arrays on equality. Better way?
+        # changed order in gaining messes this up. Just ignore?
+        equal = true
+        if (defaultGained.length != nowGained.length)
+          equal = false
+        else
+          for card, index in defaultGained
+            equal = (equal and (nowGained[index] == card))
+
+        if (equal)
+          putBack.push card
+

-      # Don't put last Potion back if Alchemists are in play
-      #
-      if (my.countInPlay(state.cardInfo["Alchemist"]) > 0)
-        potionTarget = Math.max potionTarget, 1
+      # sort by price. Should be improved
+      putBack.sort( (y,x) -> x.getCost(state) - y.getCost(state) )

-      putBack = (card for card in my.hand when (card.isTreasure and (card.getCoins(state) <= (coinEstimate - coinTarget)) and (card.getPotion(state) <= (potionEstimate - potionTarget)) ) ).sort( (b, a) -> (state.cardInfo[a].getCoins(state) + 2.5*state.cardInfo[a].getPotion(state) - state.cardInfo[b].getCoins(state) - 2.5*state.cardInfo[b].getPotion(state)) )
+      # Don't put back last Potion if Alchemists are in play
+      if my.countInPlay(state.cardInfo["Alchemist"])>0
+        if my.countInHand(state.cardInfo["Potion"]) == 1
+          putBack.remove(state.cardInfo["Potion"])

+      # Think about Royal Seal
+
     # 4) Put back the worst card (take priority for discard)
     #
     if putBack.length==0


misc/webapp: special case for logger wrapper for string vs. object

--- src/smc-util/misc.coffee
+++ src/smc-util/misc.coffee
@@ -1489,7 +1489,12 @@
     dt = seconds2hms(smc_logger_timestamp - smc_logger_timestamp_last)
     # support for string interpolation for the actual console.log
     [msg, args...] = Array.prototype.slice.call(arguments)
-    console.log_original("[#{t} Δ #{dt}] #{msg}", args...)
+    prompt = "[#{t} Δ #{dt}]"
+    if _.isString(msg)
+        prompt = "#{prompt} #{msg}"
+        console.log_original(prompt, args...)
+    else
+        console.log_original(prompt, msg, args...)
     smc_logger_timestamp_last = smc_logger_timestamp

 if not exports.RUNNING_IN_NODE and window?


Use fake group names for map on summary screen

--- app/annotation/summary.cjsx
+++ app/annotation/summary.cjsx
@@ -8,10 +8,10 @@
 steps = require '../lib/steps'

 sites =
-  siteA: ["guinea_foutahdjalon", "guinea_sangaredi", "guineabissau_boe_part1", "guineabissau_boe_part2", "ivorycoast_azagny", "ivorycoast_djouroutou", "ivorycoast_taie", "ivorycoast_tair", "liberia_eastnimba", "liberia_grebo_sala_part1", "liberia_grebo_sala_part2", "liberia_grebo_tasla", "liberia_sapo", "senegal_kayan", "liberia_grebo_part1", "taieast_marielyne"]
-  siteB: ["nigeria_gashaka"]
-  siteC: ["equatorialguinea_nationwide"]
-  siteD: ["drc_biliuere", "uganda_budongo", "uganda_bwindi", "uganda_ngogo", "tanzania_ugalla"]
+  siteA: ["dry-lake", "aged-violet", "cold-snowflake", "cool-silence", "red-water", "crimson-dew", "lingering-shape", "frosty-sky", "muddy-frost", "lingering-shape", "dark-field", "dawn-bird"]
+  siteB: ["wandering-sun"]
+  siteC: ["shy-waterfall"]
+  siteD: ["green-snowflake", "quiet-wood", "delicate-river", "cold-fire", "restless-star"]

 # Note: group metadata does not exist in dev db, so buttons and location image will not work locally or on staging
 Summary = React.createClass
@@ -49,7 +49,7 @@
     console.error 'Project Group fetch failed'

   getSiteLocation: ->
-    locationName = @state.group.metadata.site
+    locationName = @state.group.metadata.fake_name

     siteLocation = switch
       when sites.siteA.indexOf(locationName.toLowerCase()) > -1 then "site-a"
@@ -113,4 +113,4 @@
       }
     </div>

-module.exports = Summary+module.exports = Summary


add function revealLetter

add function revealLetter

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -34,6 +34,9 @@
   emptyInput = () ->
     $("input#input_answer").val("")

+  revealLetter = () ->
+    i = randomLetter()
+
   update_keyHandler = () ->
     $("#input_answer").off()
     $("#input_answer").keydown((e) =>


Better angularJS detection.

Better angularJS detection.

See @mrmr1993's comment in 2cc7dc1d2164b5dbb27bcc1d4bc0517402dd7581.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -169,7 +169,7 @@

     # Check for AngularJS listeners on the element.
     @checkForAngularJs ?= do ->
-      angularElements = document.body.getElementsByClassName "ng-scope"
+      angularElements = document.getElementsByClassName "ng-scope"
       if angularElements.length == 0
         -> false
       else


Fix typo

Fix typo

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -188,7 +188,7 @@
             handlerStack.remove()
         })
       else # event.keyCode == keyCodes.ctrlKey
-        @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_TAB else OPEN_IN_NEW_FG_TAB_TAB)
+        @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_TAB else OPEN_IN_NEW_FG_TAB)

         handlerStack.push({
           keyup: (event) =>


add test for issue #5

add test for issue #5

--- test/gerber-parser_test.coffee
+++ test/gerber-parser_test.coffee
@@ -37,6 +37,13 @@
       (-> p.parseCommand(param 'FSpoop')).should.throw /invalid/

   describe 'parsing an aperture definition', ->
+    it 'should remove leading zeros from the tool code string', ->
+      p.parseCommand(param 'ADD010C,1').should.eql {
+        tool: { D10: { dia: 1 } }
+      }
+      p.parseCommand(param 'ADD0011C,1').should.eql {
+        tool: { D11: { dia: 1 } }
+      }
     describe 'with standard tools', ->
       it 'should handle standard circles', ->
         p.parseCommand(param 'ADD10C,1').should.eql {


Fixing deployment status hook

Fixing deployment status hook


use already existing parse model

use already existing parse model

--- src/storage/models/user.coffee
+++ src/storage/models/user.coffee
@@ -1,7 +1,7 @@
 angular.module 'Scrumble.storage'
 .factory 'ScrumbleUser', (Parse, $q, TrelloClient, Project, localStorageService) ->
-  class ScrumbleUser extends Parse.Model
-    @configure "ScrumbleUser", "email", "project"
+  class NotSoShittyUser extends Parse.Model
+    @configure "NotSoShittyUser", "email", "project"

     @getCurrentUser = ->
       @query(


Revert "Fixed i18n-coverage bug"

Revert "Fixed i18n-coverage bug"

This reverts commit 97cdb76b825b7a8a64ad0356d6527aa35bddd6b4.

--- app/models/CocoModel.coffee
+++ app/models/CocoModel.coffee
@@ -379,26 +379,8 @@
   updateI18NCoverage: ->
     i18nObjects = @findI18NObjects()
     return unless i18nObjects.length
-
-    langCodeArrays = (_.keys(i18n) for i18n in i18nObjects)
-    langCodeArray = langCodeArrays[0]
-    temp_i18nCoverage = []
-    schemaProps = (schemaProp for schemaProp in this.schema().properties.i18n.props when this.attributes.hasOwnProperty(schemaProp))
-
-    for lang in langCodeArray
-
-      langProps = i18n[lang]
-      allProps = true
-
-      for prop in schemaProps
-        if not langProps.hasOwnProperty(prop)
-          allProps = false
-          break
-
-      if allProps
-        temp_i18nCoverage.push lang
-
-    @set('i18nCoverage', temp_i18nCoverage)
+    langCodeArrays = (_.keys(i18n) for i18n in i18nObjects)
+    @set('i18nCoverage', _.intersection(langCodeArrays...))

   findI18NObjects: (data, results) ->
     data ?= @attributes


when the uploaded file is not a zip, warn instead of error

--- services/web/app/coffee/Features/Uploads/ArchiveManager.coffee
+++ services/web/app/coffee/Features/Uploads/ArchiveManager.coffee
@@ -33,7 +33,7 @@
 		unzip.on "close", (exitCode) ->
 			if error?
 				error = new Error(error)
-				logger.error err:error, source: source, "error checking zip size"
+				logger.warn err:error, source: source, "error checking zip size"

 			lines = output.split("\n")
 			lastLine = lines[lines.length - 2]?.trim()


Remove stray debugger

Remove stray debugger

--- src/theme-manager.coffee
+++ src/theme-manager.coffee
@@ -10,9 +10,6 @@
 module.exports =
 class ThemeManager
   constructor: ({@packageManager, @resourcePath, @configDirPath, @safeMode, @config, @styleManager, @notificationManager, @viewRegistry}) ->
-    unless @packageManager
-      debugger
-
     @emitter = new Emitter
     @styleSheetDisposablesBySourcePath = {}
     @lessCache = null


Revert "fix: benchmark process won't exit"

This reverts commit 1026cef98c00723f26fe32b19ae5b08faf496952.

--- benchmarks/clients/client_process.coffee
+++ benchmarks/clients/client_process.coffee
@@ -530,7 +530,7 @@
         opts.eventDescriptors = eventDescriptors
         clientProcess = new ClientProcess(opts)
         clientProcess.start()
-        next()
+        clientProcess.once("started", next)
     (next)->
         resultLogger("start benchmark...")
         clientProcess.startBenchmark()


Changed total_amount computation from tax*pretax into pretax*tax+pretax (#494)

--- app/assets/javascripts/backend/trade.js.coffee
+++ app/assets/javascripts/backend/trade.js.coffee
@@ -118,7 +118,7 @@
       # Compute unit_pretax_amount
       updates.unit_pretax_amount = E.trade.round(values.pretax_amount / (values.quantity * (100.0 - values.reduction_percentage) / 100.0), 2)
       # Compute amount
-      updates.amount = E.trade.round(values.pretax_amount * values.tax, 2)
+      updates.amount = E.trade.round(values.pretax_amount + (values.pretax_amount) * values.tax, 2)
       E.trade.itemValues(item, updates)

     # Compute other amounts from amount


JS: common_interface clean-up. Closes #1180

--- app/assets/javascripts/common_interface.coffee
+++ app/assets/javascripts/common_interface.coffee
@@ -1,74 +1,21 @@
-$ ->
-  # TODO: check if this is still needed, was added for an old bug - PZ
-  if _.include(['iPad', 'iPhone', 'iPod'], navigator.platform)
-    $("#footer").css("position", "static")
-
-  if $.browser.msie
-    # A 1px increase fixes some bad aliasing when resizing the image down
-    # to non-HiDPI resolutions.
-    $('#header_inside img[src$="@2x.png"]').attr(width: '401')
-
-  # Adds "Search" to the search field for browsers which do not support the
-  # "placeholder" attribute.
-  unless Modernizr.input.placeholder
-    search      = $("#header_inside input[name=q]")
-    placeholder = search.attr('placeholder')
-
-    if search.val()?.length is 0
-      search.val(placeholder)
-
-    search.focus ->
-      # Remove the search field value only if it matches the placeholder;
-      # we do not want to erase the users search text.
-      search.val('') if search.val() is placeholder
-
-    search.blur ->
-      value = search.val() or ''
-
-      # Similarly, only add the placeholder back if the user did not enter
-      # a value.
-      if value.length is 0 or value is placeholder or not value.match(/\S/)
-        search.val(placeholder)
-
-  # locale select box
-  $("select#locale").change ->
-    $.ajax
-      url: "/set_locale"
-      method: "PUT"
-      data:
-        locale: $(this).val()
-      success: -> window.location.reload()
-
-  # Is this thing still used?
-  $("select[name='area_code']").change ->
-    country = $(this).val()
-    url = "/update_footer/?country=" + country
-    $.ajax
-      url: url
-      method: 'get'
-      success: (data) ->
-        $("#logos").replaceWith(data)
-        Interface.call_the_cyclists()
-
-  # Tooltips. Works with AJAX-injected content, too
-  #
-  $(document).on 'mouseover', 'a.tooltip', (e) ->
-    $(this).qtip {
-      overwrite: false
-      content: -> $(this).attr('title')
-      show:
-        event: e.type
-        ready: true
-      position:
-        my: 'bottom right'
-        at: 'top center'
-      style:
-        classes: "ui-tooltip-tipsy"
-      }, e
-
-class @AppInterface
+class @CommonInterface
   constructor: ->
     @setup_menus()
+    @setup_home_menu()
+    @setup_tooltips()
+    @setup_browser_tweaks()
+    @call_the_cyclists()
+
+  setup_home_menu: ->
+    $("select[name='area_code']").change ->
+      country = $(this).val()
+      url = "/update_footer/?country=" + country
+      $.ajax
+        url: url
+        method: 'get'
+        success: (data) ->
+          $("#logos").replaceWith(data)
+          Interface.call_the_cyclists()

   set_active_movie_tab: (page) ->
     $(".movie_tabs li").removeClass 'active'
@@ -83,6 +30,8 @@
     else
       $('#other_year').hide()

+  # footer logos rotation
+  #
   call_the_cyclists: ->
     if $("#logos ul.left li").length > 1
       $("#logos ul.left").cycle
@@ -125,11 +74,67 @@
       if App.peak_load
         App.peak_load.disable_peak_load_tracking()

+    $("select#locale").change ->
+      $.ajax
+        url: "/set_locale"
+        method: "PUT"
+        data:
+          locale: $(this).val()
+        success: -> window.location.reload()
+
+
   close_all_menus: ->
     $('a.menu_toggler').removeClass('menu-open')
     $('.header_menu').hide()

+  # Tooltips. Works with AJAX-injected content, too
+  #
+  setup_tooltips: ->
+    $(document).on 'mouseover', 'a.tooltip', (e) ->
+      $(this).qtip {
+        overwrite: false
+        content: -> $(this).attr('title')
+        show:
+          event: e.type
+          ready: true
+        position:
+          my: 'bottom right'
+          at: 'top center'
+        style:
+          classes: "ui-tooltip-tipsy"
+        }, e
+
+  setup_browser_tweaks: ->
+    # TODO: check if this is still needed, was added for an old bug - PZ
+    if _.include(['iPad', 'iPhone', 'iPod'], navigator.platform)
+      $("#footer").css("position", "static")
+
+    if $.browser.msie
+      # A 1px increase fixes some bad aliasing when resizing the image down
+      # to non-HiDPI resolutions.
+      $('#header_inside img[src$="@2x.png"]').attr(width: '401')
+
+    # Adds "Search" to the search field for browsers which do not support the
+    # "placeholder" attribute.
+    unless Modernizr.input.placeholder
+      search      = $("#header_inside input[name=q]")
+      placeholder = search.attr('placeholder')
+
+      if search.val()?.length is 0
+        search.val(placeholder)
+
+      search.focus ->
+        # Remove the search field value only if it matches the placeholder;
+        # we do not want to erase the users search text.
+        search.val('') if search.val() is placeholder
+
+      search.blur ->
+        value = search.val() or ''
+
+        # Similarly, only add the placeholder back if the user did not enter
+        # a value.
+        if value.length is 0 or value is placeholder or not value.match(/\S/)
+          search.val(placeholder)

 $ ->
-  window.Interface = new AppInterface()
-  Interface.call_the_cyclists()
+  window.Interface = new CommonInterface()


check if the user being removed is the last owner of the room

--- server/methods/removeUserFromRoom.coffee
+++ server/methods/removeUserFromRoom.coffee
@@ -13,6 +13,11 @@

 		if data.username not in (room?.usernames or [])
 			throw new Meteor.Error 'error-user-not-in-room', 'User is not in this room', { method: 'removeUserFromRoom' }
+
+		if RocketChat.authz.hasRole(Meteor.user()._id, 'owner', room._id)
+			numOwners = RocketChat.authz.getUsersInRole('owner', room._id).fetch().length
+			if numOwners is 1
+				throw new Meteor.Error 'error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { method: 'leaveRoom' }

 		removedUser = RocketChat.models.Users.findOneByUsername data.username


syntax error, smaller number of points plotted

--- bokehjs/demo/coffee/scatter.coffee
+++ bokehjs/demo/coffee/scatter.coffee
@@ -1,6 +1,6 @@
 require(['main'], (Bokeh) ->

-  N= 20000
+  N= 2000
   r = new Bokeh.Random(123456789)

   x = (r.randf()*100 for i in _.range(N))
@@ -35,4 +35,6 @@
   }

   plot = Bokeh.Plotting.make_plot(scatter, data, options)
-  Bokeh.Plotting.show(plot))+  Bokeh.Plotting.show(plot)
+  )
+


fix missing formatUrl call

fix missing formatUrl call

--- server/methods.coffee
+++ server/methods.coffee
@@ -22,8 +22,6 @@
     params =
       api_key: "Cr9LPAtL"
       returnSourceContent: true
-      # formatUrl takes a database cleanUrl and adds 'http://'
-      url: formatUrl(article.url)
     if article.publishDate or article.addedDate
       params.content_date = moment.utc(
         article.publishDate or article.addedDate
@@ -31,7 +29,8 @@
     if article.content
       params.content = article.content
     else if article.url
-      params.url = article.url
+      # formatUrl takes a database cleanUrl and adds 'http://'
+      params.url = formatUrl(article.url)
     else
       Meteor.Error("InvalidArticle", "Content or a URL must be specified")
     result = HTTP.post(GRITS_API_URL + "/public_diagnose", params: params)


small fix

small fix

--- build/02-csv-ndjson.coffee
+++ build/02-csv-ndjson.coffee
@@ -28,7 +28,7 @@
 		this.queue
 			id:				parseInt data.route_id
 			name:			data.route_short_name
-			agencyId:		data.agency_id
+			agencyId:		parseAgency data.agency_id

 	'transfers.csv': (data) ->
 		this.queue


Oscillator

Oscillator


Make sure constructor is available too

Make sure constructor is available too

--- test/robot_test.coffee
+++ test/robot_test.coffee
@@ -35,7 +35,7 @@

     # Re-throw AssertionErrors for clearer test failures
     @robot.on 'error', (name, err, response) ->
-      return unless err?
+      return unless err?.constructor?
       if err.constructor.name == "AssertionError"
         process.nextTick () ->
           throw err


Refactor Robot unit tests

Refactor Robot unit tests

--- test/robot_test.coffee
+++ test/robot_test.coffee
@@ -136,46 +136,46 @@
         expect(match2).to.equal('message123')

     describe '#listen', ->
-      it 'registers a new listener', ->
+      it 'registers a new listener directly', ->
+        expect(@robot.listeners).to.have.length(0)
+        @robot.listen (->), ->
+        expect(@robot.listeners).to.have.length(1)
+
+    describe '#hear', ->
+      it 'registers a new listener directly', ->
         expect(@robot.listeners).to.have.length(0)
         @robot.hear /.*/, ->
         expect(@robot.listeners).to.have.length(1)

-    describe '#hear', ->
-      it 'registers a new listener', ->
-        expect(@robot.listeners).to.have.length(0)
-        @robot.hear /.*/, ->
-        expect(@robot.listeners).to.have.length(1)
-
     describe '#respond', ->
-      it 'registers a new listener', ->
-        expect(@robot.listeners).to.have.length(0)
+      it 'registers a new listener using hear', ->
+        sinon.spy @robot, 'hear'
         @robot.respond /.*/, ->
-        expect(@robot.listeners).to.have.length(1)
+        expect(@robot.hear).to.have.been.called

     describe '#enter', ->
-      it 'registers a new listener', ->
-        expect(@robot.listeners).to.have.length(0)
+      it 'registers a new listener using listen', ->
+        sinon.spy @robot, 'listen'
         @robot.enter ->
-        expect(@robot.listeners).to.have.length(1)
+        expect(@robot.listen).to.have.been.called

     describe '#leave', ->
-      it 'registers a new listener', ->
-        expect(@robot.listeners).to.have.length(0)
+      it 'registers a new listener using listen', ->
+        sinon.spy @robot, 'listen'
         @robot.leave ->
-        expect(@robot.listeners).to.have.length(1)
+        expect(@robot.listen).to.have.been.called

     describe '#topic', ->
-      it 'registers a new listener', ->
-        expect(@robot.listeners).to.have.length(0)
+      it 'registers a new listener using listen', ->
+        sinon.spy @robot, 'listen'
         @robot.topic ->
-        expect(@robot.listeners).to.have.length(1)
+        expect(@robot.listen).to.have.been.called

     describe '#catchAll', ->
-      it 'registers a new listener', ->
-        expect(@robot.listeners).to.have.length(0)
+      it 'registers a new listener using listen', ->
+        sinon.spy @robot, 'listen'
         @robot.catchAll ->
-        expect(@robot.listeners).to.have.length(1)
+        expect(@robot.listen).to.have.been.called

     describe '#receive', ->
       it 'calls all registered listeners', ->


enforced scalar identity elements, issue #16

RANK ERROR is raised if the argument to ⍁ is not a singleton.
A non-scalar singleton is converted to a scalar.

--- src/vocabulary/identity.coffee
+++ src/vocabulary/identity.coffee
@@ -1,6 +1,7 @@
 {assert} = require '../helpers'
 {APLArray} = require '../array'
 {withIdentity} = require './vhelpers'
+{RankError} = require '../errors'

 # Identity operator (`⍁`)
 #
@@ -8,8 +9,12 @@
 # f←{⍺+2×⍵} ⋄ (f⍁123)/⍬     <=> 123
 # f←{⍺+2×⍵} ⋄ (456⍁f)/⍬     <=> 456
 # f←{⍺+2×⍵} ⋄ g←f⍁789 ⋄ f/⍬ !!! DOMAIN ERROR
+# {}⍁1 2                    !!! RANK ERROR
+# ({}⍁(1 1 1⍴123))/⍬        <=> 123
 @['⍁'] = (f, x) ->
   if f instanceof APLArray then [f, x] = [x, f]
   assert typeof f is 'function'
   assert x instanceof APLArray
+  if not x.isSingleton() then throw RankError()
+  if x.shape.length then x = APLArray.scalar x.unwrap()
   withIdentity x, (omega, alpha, axis) -> f omega, alpha, axis


main() function

main() function

--- code/rss.coffee
+++ code/rss.coffee
@@ -39,5 +39,10 @@
 allDone = ->
   fs.writeFile 'loggit-rss.xml', feed.xml(), ->

-# "loop" over data and add to feed
-db.each "select * from loggit_event where type='start'", eachRow, allDone
+main = ->
+  # "loop" over data and add to feed
+  db.each "select * from loggit_event where type='start'", eachRow, allDone
+
+# START
+if process.argv[1].match /rss\.coffee$/
+  main()


Restore concurrency just on OSX

Restore concurrency just on OSX

--- tasks/spec-task.coffee
+++ tasks/spec-task.coffee
@@ -52,6 +52,9 @@
       continue unless isAtomPackage(packagePath)
       packageSpecQueue.push(packagePath)

+    # TODO: Restore concurrency on Windows
+    packageSpecQueue.concurrency = 1 unless process.platform is 'win32'
+
     packageSpecQueue.drain = -> callback(null, failedPackages)

   runCoreSpecs = (callback) ->
@@ -76,13 +79,24 @@
       if process.platform is 'win32'
         process.stdout.write(fs.readFileSync('ci.log'))
         fs.unlinkSync('ci.log')
+      else
+        # TODO: Restore concurrency on Windows
+        packageSpecQueue.concurrency = 2
+
       callback(null, error)

   grunt.registerTask 'run-specs', 'Run the specs', ->
     done = @async()
     startTime = Date.now()

-    async.series [runCoreSpecs, runPackageSpecs], (error, results) ->
+    # TODO: This should really be parallel on both platforms, however our
+    # fixtures step on each others toes currently.
+    if process.platform is 'darwin'
+      method = async.series
+    else if process.platform is 'win32'
+      method = async.parallel
+
+    method [runCoreSpecs, runPackageSpecs], (error, results) ->
       [coreSpecFailed, failedPackages] = results
       elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
       grunt.verbose.writeln("Total spec time: #{elapsedTime}s")
@@ -91,6 +105,7 @@

       grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0

+      # TODO: Mark the build as green on Windows until specs pass.
       if process.platform is 'darwin'
         done(!coreSpecFailed and failedPackages.length == 0)
       else if process.platform is 'win32'


Tweak offline page error message

Tweak offline page error message

--- assets/javascripts/templates/error_tmpl.coffee
+++ assets/javascripts/templates/error_tmpl.coffee
@@ -23,9 +23,9 @@
             If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """

 app.templates.offlineError = ->
-  error """ Your browser is unsupported, sorry. """,
-        """ DevDocs uses IndexedDB to cache complete documentations for offline access.

-            Unfortunately your browser either doesn't support IndexedDB, or its implementation is buggy. """
+  error """ Oops, the database failed to load. """,
+        """ DevDocs requires IndexedDB to cache documentations for offline access.

+            Unfortunately IndexedDB is either not supported in your browser, disabled, or buggy. """

 app.templates.unsupportedBrowser = """


Changed to use forEach instead of $push.

--- tasks/cullMatches.coffee
+++ tasks/cullMatches.coffee
@@ -7,20 +7,15 @@
 _ = require('lodash')

-Data.where()
-  .setOptions({multi: true})
-  .update(
-    {
-      $push: {
-        gameLog: {
-          $each: []
-          $sort: {
-            '_date': 1
-          }
-          $slice: -100
-        }
-      }
-    },
-    (err, num)->
-      console.log err, num
-  )+Data.find().forEach((doc)->
+  doc.gameLog.sort((a, b)->
+    aDate = new Date(a._date)
+    bDate = new Date(b._date)
+    # Descending order. Earliest first.
+    return bDate.getTime() - aDate.getTime()
+    )
+  newGameLog = doc.gameLog.slice(0, 100)
+  Data.update({'playerName':doc.playerName}, {'gameLog':newGameLog}, (err, num)->
+      console.log(err, num)
+    )
+)


IDE: chat pane forces every message to have collaboration:yes payload

--- client/ide/views/chat/messagepane.coffee
+++ client/ide/views/chat/messagepane.coffee
@@ -16,3 +16,12 @@
       top      : '16px'
       right    : '16px'
       'z-index': 12
+
+
+  createInputWidget: ->
+
+    channel = @getData()
+    @input  = new ReplyInputWidget {channel, collaboration : yes, cssClass : 'private'}
+
+    @input.on 'EditModeRequested', @bound 'editLastMessage'
+


Use defer to prevent to race condition

Use defer to prevent to race condition

--- client/pricing/lib/pricingappview.coffee
+++ client/pricing/lib/pricingappview.coffee
@@ -194,31 +194,33 @@

   preventBlockedUser: (options, callback) ->

-    @appStorage.fetchValue KEY, (result) =>
-
-      return callback()  unless result
-
-      difference = Date.now() - result.timestamp
-
-      if difference < DURATION
-
-        @workflowController = new PaymentWorkflow { state: options, delegate: this }
-
-        @workflowController.on PaymentConstants.events.WORKFLOW_STARTED, =>
-          @state.inProcess = no
-          @workflowController.failedAttemptLimitReached no
-
-        workflowCouldNotStart = PaymentConstants.events.WORKFLOW_COULD_NOT_START
-
-        @workflowController.on workflowCouldNotStart, =>
-          @emit PaymentConstants.events.WORKFLOW_COULD_NOT_START
-          @state.inProcess = no
-
-        return
-
-      @removeBlockFromUser()
-
-      return callback()
+    kd.utils.defer =>
+
+      @appStorage.fetchValue KEY, (result) =>
+
+        return callback()  unless result
+
+        difference = Date.now() - result.timestamp
+
+        if difference < DURATION
+
+          @workflowController = new PaymentWorkflow { state: options, delegate: this }
+
+          @workflowController.on PaymentConstants.events.WORKFLOW_STARTED, =>
+            @state.inProcess = no
+            @workflowController.failedAttemptLimitReached no
+
+          workflowCouldNotStart = PaymentConstants.events.WORKFLOW_COULD_NOT_START
+
+          @workflowController.on workflowCouldNotStart, =>
+            @emit PaymentConstants.events.WORKFLOW_COULD_NOT_START
+            @state.inProcess = no
+
+          return
+
+        @removeBlockFromUser()
+
+        return callback()

   removeBlockFromUser: ->


ComputeUtils: make group plan fetcher part of revive if requested

--- workers/social/lib/social/models/computeproviders/computeutils.coffee
+++ workers/social/lib/social/models/computeproviders/computeutils.coffee
@@ -67,9 +67,12 @@
     JCredential.fetchByIdentifier client, credential, callback

-reviveClient = (client, callback, revive = yes) ->
-
-  return callback null  unless revive
+reviveClient = (client, callback, options) ->
+
+  { shouldReviveClient, shouldFetchGroupPlan } = options ? {}
+  shouldReviveClient ?= yes
+
+  return callback null  unless shouldReviveClient

   { connection: { delegate:account }, context: { group } } = client

@@ -91,7 +94,16 @@

       res.user = user

-      callback null, res
+      if shouldFetchGroupPlan
+
+        reviveGroupPlan res.group, (err, group) ->
+          return callback err  if err
+          res.group = group
+          callback null, res
+
+      else
+
+        callback null, res

 reviveOauth = (client, oauthProvider, callback) ->
@@ -132,6 +144,7 @@
     shouldPassCredential
     shouldReviveProvider
     shouldReviveProvisioners
+    shouldFetchGroupPlan
     shouldLockProcess
     shouldHaveOauth
     hasOptions
@@ -193,7 +206,6 @@

         return

-
       # This is Koding only which doesn't need a valid credential
       # since the user session is enough for koding provider for now.

@@ -223,7 +235,7 @@

         , shouldReviveProvisioners

-    , shouldReviveClient
+    , { shouldReviveClient, shouldFetchGroupPlan }

 checkTemplateUsage = (template, account, callback) ->
@@ -281,6 +293,8 @@

         res.template = template
         callback null, res
+
+  , { shouldFetchGroupPlan: yes }

 guessNextLabel = (options, callback) ->


updated test stuff

updated test stuff

--- core/test.coffee
+++ core/test.coffee
@@ -11,7 +11,28 @@

 class TestManager
     constructor : (@mewbot)->
-
+
+    listAvaliableTests : (callback)->
+        Fs.readdir Path.join(__dirname,"..","testrc"),(err,files)=>
+            if err
+                callback(err)
+            else
+                testsAvailable = []
+                testFileShiftCallback = =>
+                    file = files.shift()
+                    if file
+                        if Path.extname(file) is ".coffee"
+                            Fs.stat Path.join(__dirname,"..","testrc",file),(err,stat)=>
+                                if stat.isDirectory() is false
+                                    testsAvailable.push file.replace(/\.coffee/g,"")
+                                testFileShiftCallback()
+                        else
+                            testFileShiftCallback()
+                    else
+                        callback(null,testsAvailable)
+                testFileShiftCallback()
+
+
     runTest : (testScriptArray,callback)->
         testExecResult = { testcase : {} , success : 0 ,error : 0 , failed : 0}
         runTestCallback = =>


allow unicity of bind

allow unicity of bind

--- BackofficeBundle/Resources/public/coffee/extendView/submitAdmin.coffee
+++ BackofficeBundle/Resources/public/coffee/extendView/submitAdmin.coffee
@@ -7,28 +7,27 @@
     tinymce.triggerSave()
     viewContext = @
     viewClass = appConfigurationView.getConfiguration(@options.entityType, @options.formView)
-    @button = $(event.target).parent() if !@button?
+    @button = $(event.target).parent() if event.originalEvent
     form = $(event.target).closest('form')
     if form.length == 0 && (clone = $(event.target).data('clone'))
       $('#' + clone).click()
     else
-      button = @button
-      @button = null
-      form.unbind()
-      form.submit ->
-        event.preventDefault()
-        form.ajaxSubmit
-          context:
-            button: button
-          success: (response) ->
-            new viewClass(viewContext.addOption(
-              html: response
-              submitted: true
-            ))
-          error: (response) ->
-            new viewClass(viewContext.addOption(
-              html: response.responseText
-            ))
-        false
+      if !form.hasClass('HTML5validation')
+        form.addClass('HTML5validation')
+        form.submit ->
+          event.preventDefault()
+          form.ajaxSubmit
+            context:
+              button: viewContext.button
+            success: (response) ->
+              new viewClass(viewContext.addOption(
+                html: response
+                submitted: true
+              ))
+            error: (response) ->
+              new viewClass(viewContext.addOption(
+                html: response.responseText
+              ))
+          false
     return
 }


reactivate HTML5 validation

reactivate HTML5 validation

--- BackofficeBundle/Resources/public/coffee/extendView/submitAdmin.coffee
+++ BackofficeBundle/Resources/public/coffee/extendView/submitAdmin.coffee
@@ -11,6 +11,8 @@
     if form.length == 0 && (clone = $(event.target).data('clone'))
       $('#' + clone).click()
     else
+      if $("textarea.tinymce", form).length > 0
+        tinymce.triggerSave()
       if !form.hasClass('HTML5validation')
         form.addClass('HTML5validation')
         form.submit ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -28,6 +28,6 @@
   player.I.y = 800
   player.update()

-  equals player.I.x, App.height - player.I.width / 2
+  equals player.I.x, App.height - player.I.height / 2

 module()


PR #3658 - Fixes mistypes in article_view.coffee

--- app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee
@@ -274,7 +274,7 @@
       offsetTop = signatureMarker.next('div, p, br').position()

     # remember bubble content height
-    bubbleContentHeigth = bubbleContent.height()
+    bubbleContentHeight = bubbleContent.height()

     # get marker height
     if offsetTop
@@ -282,18 +282,18 @@

     # if signature marker exists and height is within maxHeight
     if markerHeight && markerHeight < maxHeight
-      newHeigth = markerHeight + 30
-      if newHeigth < minHeight
-        newHeigth = minHeight
-
-      bubbleContent.attr('data-height', bubbleContentHeigth + 30)
-      bubbleContent.attr('data-height-origin', newHeigth)
-      bubbleContent.css('height', "#{newHeigth}px")
+      newHeight = markerHeight + 30
+      if newHeight < minHeight
+        newHeight = minHeight
+
+      bubbleContent.attr('data-height', bubbleContentHeight + 30)
+      bubbleContent.attr('data-height-origin', newHeight)
+      bubbleContent.css('height', "#{newHeight}px")
       bubbleOverflowContainer.removeClass('hide')

     # if height is higher then maxHeight
-    else if bubbleContentHeigth > maxHeight
-      bubbleContent.attr('data-height', bubbleContentHeigth + 30)
+    else if bubbleContentHeight > maxHeight
+      bubbleContent.attr('data-height', bubbleContentHeight + 30)
       bubbleContent.attr('data-height-origin', maxHeight)
       bubbleContent.css('height', "#{maxHeight}px")
       bubbleOverflowContainer.removeClass('hide')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -88,6 +88,6 @@
   equals player.I.y, 20

 test "should move player according to their speed", ->
-  player.I.spee
+  player.I.speed = 3

 module()


Create the UIWatcher after everything has been loaded

--- lib/dev-live-reload.coffee
+++ lib/dev-live-reload.coffee
@@ -1,10 +1,16 @@
+$ = require 'jquery'
 UIWatcher = require './ui-watcher'

 module.exports =
   activate: (state) ->
-    uiWatcher = new UIWatcher
-      themeManager: atom.themes
+
+    # HACK: I need an actvation event when the ui or packages are all loaded.
+    # It cant watch all the packages until they are all loaded.
+    createUIWatcher = ->
+      uiWatcher = new UIWatcher
+        themeManager: atom.themes
+      $(window).off 'focus', createUIWatcher
+    $(window).on 'focus', createUIWatcher

     rootView.command 'dev-live-reload:reload-all', ->
       uiWatcher.reloadAll()
-


Use proxy_pass'd api

Use proxy_pass'd api

--- project.coffee
+++ project.coffee
@@ -9,8 +9,16 @@
 teampage = require './templates/team-page'
 subjectGroup = localStorage.getItem 'active-group'

+[apiHost, apiProxyPath] = if window.location.hostname is 'www.sciencegossip.org'
+  ['http://www.sciencegossip.org', '/_ouroboros_api/proxy']
+else
+  [null, null]
+
 module.exports =
   id: 'illustratedlife'
+  apiHost: apiHost
+  apiProxyPath: apiProxyPath
+
   background: 'background.jpg'
   subjectGroup: subjectGroup ? 'random'
   groups: group_page.groups


Add a skip option

Add a skip option

--- project.coffee
+++ project.coffee
@@ -49,6 +49,9 @@
       },{
         label: 'No'
         value: 'no'
+      },{
+        label: 'Skip this page'
+        value: 'skip'
       }]
     illustration:
       type: 'drawing'


added fedex and usps test cases for trk number variations

--- test/guessCarrier.coffee
+++ test/guessCarrier.coffee
@@ -6,7 +6,7 @@

 describe 'guesses shipment carrier', ->

-  describe 'for UPS tracking numbers', ->
+  describe 'for UPS', ->

     it 'detects a good UPS tracking number', ->
       expect(guessCarrier '1Z6V86420323794365').to.include 'ups'
@@ -20,3 +20,63 @@

     it 'strips spaces and then detects', ->
       expect(guessCarrier '1Z 6V86 4203 2379 4365').to.include 'ups'
+
+
+  describe 'for FedEx', ->
+
+    it 'detects a 12 digit fedex tracking number', ->
+      expect(guessCarrier '771613423732').to.include 'fedex'
+
+    it 'detects a 12 digit fedex tracking number with spaces', ->
+      expect(guessCarrier '7716 1342 3732').to.include 'fedex'
+
+    it 'detects a fedex door tag number', ->
+      expect(guessCarrier 'DT771613423732').to.include 'fedex'
+
+    it 'detects a fedex 15 digit tracking number', ->
+      expect(guessCarrier '997048950367429').to.include 'fedex'
+
+    it 'detects a fedex 15 digit tracking number with spaces', ->
+      expect(guessCarrier '9970 4895 0367 429').to.include 'fedex'
+
+    it 'detects a fedex smartpost number', ->
+      expect(guessCarrier '9274899992136003821767').to.include 'fedex'
+
+    it 'detects a fedex smartpost number with spaces', ->
+      expect(guessCarrier '92 7489 9992 1360 0382 1767').to.include 'fedex'
+
+
+  describe 'for USPS', ->
+
+    it 'detects a USPS 94+20 digit tracking number', ->
+      expect(guessCarrier '9400109699938860246573').to.include 'usps'
+
+    it 'detects a USPS 94+20 digit tracking number with spaces', ->
+      expect(guessCarrier '94 0010 9699 9388 6024 6573').to.include 'usps'
+
+    it 'detects a 420+zipcode+22 digit tracking number', ->
+      expect(guessCarrier '420921559505500020714300000128').to.include 'usps'
+
+    it 'detects a 420+zipcode+22 digit tracking number with spaces', ->
+      expect(guessCarrier '420 92155 95 0550 0020 7143 0000 0128').to.include 'usps'
+
+    it 'detects an express mail / international tracking number', ->
+      expect(guessCarrier 'EC207920162US').to.include 'usps'
+
+    it 'detects an express mail tracking number with spaces', ->
+      expect(guessCarrier 'EC 207 920 162 US').to.include 'usps'
+
+    it 'detects a 26 digit usps tracking number', ->
+      expect(guessCarrier '92023901003036542400961407').to.include 'usps'
+
+    it 'detects a fedex smartpost number', ->
+      expect(guessCarrier '9274899992136003821767').to.include 'usps'
+
+    it 'detects a fedex smartpost number with spaces', ->
+      expect(guessCarrier '92 7489 9992 1360 0382 1767').to.include 'usps'
+
+    it 'detects a ups mail innovations number', ->
+      expect(guessCarrier '92748999997295513123034457').to.include 'usps'
+
+    it 'detects a ups mail innovations number with spaces', ->
+      expect(guessCarrier '92 7489 9999 7295 5131 2303 4457').to.include 'usps'


removing the console

removing the console

--- test/keygaps.spec.coffee
+++ test/keygaps.spec.coffee
@@ -122,7 +122,6 @@
             keyFunction: (input) -> [input.category, input.x],
             valueFunction: valueFunction
         })
-        console.log(JSON.stringify(output.missingValues, null, ' '))
         expect(_.keys(output.missingValues)).to.contain("a:2")
         expect(_.keys(output.missingValues)).to.contain("b:1")
         expect(_.keys(output.missingValues)).to.contain("c:2")


Fix tests

Fix tests

--- test/rpgdice_test.coffee
+++ test/rpgdice_test.coffee
@@ -12,5 +12,8 @@

     require('../src/rpgdice')(@robot)

-  it 'registers a hear listener', ->
-    expect(@robot.hear).to.have.been.calledWith(/roll 1d6/)
+  it 'registers a respond listener for "roll"', ->
+    expect(@robot.respond).to.have.been.calledWith(/roll (?:([0-9]+)d([0-9]+))(?: (.*))*/i)
+
+  it 'registers a respond listener for "ore"', ->
+    expect(@robot.respond).to.have.been.calledWith(/ore (\d+)( \d+)?( \d+)?( .+)?/im)


add ctrl+mousewheel zooming

add ctrl+mousewheel zooming

--- lib/pdf-editor-view.coffee
+++ lib/pdf-editor-view.coffee
@@ -89,6 +89,14 @@
     @on 'mouseleave', (e) =>
       if @dragging
         @dragging = null
+
+    @on 'mousewheel', (e) =>
+      if e.ctrlKey
+        e.preventDefault
+        if e.originalEvent.wheelDelta > 0
+          @zoomIn()
+        else if e.originalEvent.wheelDelta < 0
+          @zoomOut()

   onScroll: ->
     if not @updating