finalized new appstore ui

finalized new appstore ui

--- imports/ui/appstore.cjsx
+++ imports/ui/appstore.cjsx
@@ -13,28 +13,132 @@
   boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
   textAlign: 'center'

+ApplicationDefinition = React.createClass
+  displayName: 'ApplicationDefinition'
+  componentDidMount: ->
+    editor = ace.edit $(@refs.appDefEditor).get(0)
+    editor.setOptions
+      readOnly: true
+      highlightActiveLine: false
+      highlightGutterLine: false
+    editor.setTheme "ace/theme/chrome"
+    editor.getSession().setMode "ace/mode/yaml"
+    editor.getSession().setUseWrapMode(true)
+  setEditorValue: (definition) ->
+    editor = ace.edit $(@refs.appDefEditor).get(0)
+    editor.getSession().setValue definition
+  render: ->
+

+      <form class="form-horizontal">
+

+

+

+

+

+      </form>
+

+
+ProductDetails = React.createClass
+  displayName: 'ProductDetails'
+  getInitialState: ->
+    selectedApp: @props.definitions[0]
+  changeSelectedVersion: (app) -> =>
+    @setState selectedApp: app
+    @refs.applicationDefinition.setEditorValue app.def
+  installApp: ->
+    app = @state.selectedApp
+    Meteor.call 'saveApp', app.name, app.version, app.def, =>
+      console.log 'saveAppDone'
+      $(@refs.modal).modal 'hide'
+
+  componentDidMount: ->
+    @refs.applicationDefinition.setEditorValue @state.selectedApp.def
+    $(@refs.modal).modal 'show'
+    $(@refs.modal).on 'hidden.bs.modal', (e) =>
+      @props.modalHidden?()
+  render: ->
+

+

+

+

+            <button type="button" className="close" data-dismiss="modal" aria-label="Close">×</button>
+

+

+

+
+
+

+

+

+
{@props.name}

+

+                  <button type="button" className="btn btn-default" disabled=true>version: {@state.selectedApp.version}</button>
+                  {if @props.definitions.length > 1
+                    [
+                      <button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+
+                        Select version
+                      </button>
+

+                      {@props.definitions.map (app) =>
+
{app.version}

+                      }
+

+                    ]
+                  }
+

+

+

+                <button type="button" className="btn btn-success btn-lg dropdown-toggle" data-toggle="dropdown">file_download Install</button>
+                <form role="form" className="dropdown-menu dropdown-menu-right" style={padding:10}>
+

+

+                        Installing may overwrite an existing app called {@state.selectedApp.name} with version {@state.selectedApp.version}.
+

+                        Do you wish to continue?
+

+

+                    <button onClick={@installApp} type="button" className="btn-install-app btn btn-lg btn-success pull-right">Yes!</button>
+                </form>
+

+

+

+

+                <ApplicationDefinition ref="applicationDefinition" />
+

+

+

+

+

+

+
 Product = React.createClass
   displayName: 'Product'
+  getInitialState: ->
+    showDetails: false
   onTouchTap: ->
-    console.log 'onTouchTap', @props
+    @setState showDetails: true
+  modalHidden: ->
+    @setState showDetails: false
+  listHowManyMoreVersions: ->
+    if x = @props.definitions.length - 1 then " + #{x} more"
   render: ->
-

+

{@props.name}

-
{@props.version}

+
{@props.version}{@listHowManyMoreVersions()}

         {if false

             42 installs

         }
-        {if Helpers.isAuthenticated()
-

-            system_update_alt
-

+
+        {if @state.showDetails
+          <ProductDetails {...@props} modalHidden={@modalHidden} />
         }

@@ -43,7 +147,18 @@
   displayName: 'Appstore'
   render: ->

-      {if @props.apps then @props.apps.map (app) ->
-        <Product key=app._id name=app.name version=app.version img=app.pic />
+      {if @props.apps
+        x = _.reduce @props.apps.fetch(), (prev, curr) ->
+          unless prev[curr.name]
+            prev[curr.name] =
+              name: curr.name
+              version: curr.version
+              img: curr.pic
+              definitions: [curr]
+          else prev[curr.name].definitions.push curr
+          prev
+        , {}
+        for key, app of x
+          <Product key=app.name name=app.name version=app.version img=app.img definitions=app.definitions />
       }


Relativize paths to project for folder label

--- lib/fuzzy-finder-view.coffee
+++ lib/fuzzy-finder-view.coffee
@@ -60,7 +60,7 @@
           typeClass = 'text-name'

         @span fs.base(path), class: "file label #{typeClass}"
-        if folder = fs.directory(path)
+        if folder = fs.directory(project.relativize(path))
           @span " - #{folder}/", class: 'directory'

   openPath: (path) ->


Make benchmark easier to profile in Atom

--- benchmark/benchmark.coffee
+++ benchmark/benchmark.coffee
@@ -12,12 +12,15 @@
 count = 0
 time = 0

+offsetsToInsert = []
 while count < times
-  offsets = lines.map (line, index) -> {rows: 1, characters: line.length + 1}
-  start = Date.now()
-  offsetIndex.spliceArray('rows', 0, lines.length, offsets)
-  time += Date.now() - start
+  offsetsToInsert.push lines.map (line, index) -> {rows: 1, characters: line.length + 1}
   lines = _.shuffle(lines)
   count++

+start = Date.now()
+for offsets in offsetsToInsert
+  offsetIndex.spliceArray('rows', 0, offsets.length, offsets)
+time = Date.now() - start
+
 console.log "Inserting #{lines.length * times} lines took #{time}ms (#{Math.round(lines.length * times / time)} lines/ms)"


cart-secure and button mobile

cart-secure and button mobile

--- public/views/cart_detail_view.coffee
+++ public/views/cart_detail_view.coffee
@@ -53,8 +53,8 @@

-

-

+

+

           <button class="epages-cart-overlay-checkout-button" data-i18n='checkout'></button>


fix imgur error message

fix imgur error message

--- lib/image-helper-view.coffee
+++ lib/image-helper-view.coffee
@@ -221,7 +221,7 @@
            .then (json)=>
              @setUploadedImageURL fileName, json.data.link, editor, hint, curPos
            .catch (err)=>
-              atom.notifications.addError(err.message)
+              atom.notifications.addError(err.message.message)
     else # sm.ms
       smAPI.uploadFile file.path,
         (err, url)=>


:lipstick:

:lipstick:

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -297,6 +297,7 @@
     [startScreenRow, endScreenRow] = @rowMap.screenRowRangeForBufferRow(row)
     for screenRow in [startScreenRow...endScreenRow]
       screenLine = @screenLines[screenRow]
+
       unless screenLine?
         throw new BufferToScreenConversionError "No screen line exists when converting buffer row to screen row",
           softWrapEnabled: @getSoftWrap()


The `do` isn't needed

The `do` isn't needed

--- resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
@@ -132,21 +132,20 @@

   messageTypeSelection: (type) ->
-    do (type) =>
-      iconClassesBn = 'beatmap-discussion-message-type'
-      iconClasses = iconClassesBn
+    iconClassesBn = 'beatmap-discussion-message-type'
+    iconClasses = iconClassesBn

-      if @currentType() == 'timeline' && @state.messageType == type
-        iconClasses += " #{iconClassesBn}--#{type}"
+    if @currentType() == 'timeline' && @state.messageType == type
+      iconClasses += " #{iconClassesBn}--#{type}"

-      button
-        key: type
-        className: "#{bn}__message-type"
-        onClick: => @setState messageType: type
-        div className: iconClasses,
-          el Icon, name: BeatmapDiscussionHelper.messageType.icon[type]
-          span className: "#{bn}__message-type-text",
-            Lang.get("beatmaps.discussions.message_type.#{type}")
+    button
+      key: type
+      className: "#{bn}__message-type"
+      onClick: => @setState messageType: type
+      div className: iconClasses,
+        el Icon, name: BeatmapDiscussionHelper.messageType.icon[type]
+        span className: "#{bn}__message-type-text",
+          Lang.get("beatmaps.discussions.message_type.#{type}")

   validPost: ->


Option for trigger event, defaults to blur

--- src/showErrors.coffee
+++ src/showErrors.coffee
@@ -2,7 +2,13 @@

 showErrorsModule.directive 'showErrors',
 ['$timeout', 'showErrorsConfig', ($timeout, showErrorsConfig) ->
-
+
+    getTrigger = (options) ->
+      trigger = showErrorsConfig.trigger
+      if options && options.trigger?
+        trigger = options.trigger
+      trigger
+
     getShowSuccess = (options) ->
       showSuccess = showErrorsConfig.showSuccess
       if options && options.showSuccess?
@@ -13,6 +19,7 @@
       blurred = false
       options = scope.$eval attrs.showErrors
       showSuccess = getShowSuccess options
+      trigger = getTrigger options

       inputEl   = el[0].querySelector("[name]")
       inputNgEl = angular.element(inputEl)
@@ -20,7 +27,7 @@
       unless inputName
         throw "show-errors element has no child input elements with a 'name' attribute"

-      inputNgEl.bind 'blur', ->
+      inputNgEl.bind trigger, ->
         blurred = true
         toggleClasses formCtrl[inputName].$invalid

@@ -58,11 +65,16 @@

 showErrorsModule.provider 'showErrorsConfig', ->
   _showSuccess = false
+  _trigger = 'blur'

   @showSuccess = (showSuccess) ->
     _showSuccess = showSuccess
+
+  @trigger = (trigger) ->
+    _trigger = trigger

   @$get = ->
     showSuccess: _showSuccess
+    trigger: _trigger

   return


Fix PlayState#detachedCamera and control regression.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -86,7 +86,7 @@
       @camera.updateShake()
       if @_shakeOnPlayerFall()
         @camera.unfollow()
-      else unless @camera.target? or @camera.isShaking()
+      else unless @detachedCamera or @camera.target? or @camera.isShaking()
         @camera.follow @player.cameraFocus

       @camera.updatePositionWithCursors @cursors if @detachedCamera


building the path resolver.  not working yet.

--- src/statechart.coffee
+++ src/statechart.coffee
@@ -10,18 +10,21 @@
 	@StateChart (chart) ->
 		@::__statechart = {} unless @::__statechart?

-		addStackedStates = (statesObj) ->
+		addPaths = (statesObj, parent) ->
 			for name,defn of statesObj
 				@::__statechart[name] =
-					name   : name
-					actions: defn.actions
-					methods: defn.methods
+					name       : name
+					transitions: defn.transitions
+					methods    : defn.methods
+					parent     : parent

 				# TODO: validate contents for state

-				addStackedStates defn.states
+				addPaths defn.paths

-		addStackedStates chart
+		addPaths paths
+
+		# TODO: confirm integrity of chart, makes sure all entry / exit points are accounted for

 	constructor: (config) ->
 		return unless @__statechart?
@@ -35,14 +38,15 @@

 	dispose: -> @removeAllListeners()

-
 	setState: (stateObj) ->
+		# validate state change
+
 		oldState = @__state
 		@__state = stateObj

 		@removeMethods oldState.methods
 		@addMethods @__state.methods
-		@buildActions @__state.actions
+		@buildTransitions @__state.transitions

 	removeMethods: (methods) ->
 		return unless methods?
@@ -56,21 +60,19 @@
 			@["_#{method}"] = @[method] if @[method]?
 			@[method] = impl

-	buildActions: (actions) ->
-		for action in actions
-			destination = @pathResolver action.destination
-			chgMethod = @[action.method]
-			@[action.method] = =>
+	buildTransitions: (transitions) ->
+		for t in transitions
+			destination = @pathResolver t.destination
+			chgMethod = @[t.method]
+			@[t.method] = =>
 				result = chgMethod.apply @, arguments
 				@setState = destination unless result is true

-	changeState: (statePath) ->
-		# TODO: suppport absolute and relative pathing
-		# does path exist from the current state?
-		# is this the first path?
-		unless @__state?
-
-		# change old methods out
-		# put new methods in
+	pathResolver: (path) ->
+		steps = path.split '/'
+		isRelative = path.indexOf '..' is 0

-		# wrap actions in decision functions+		for step in steps
+
+
+module.exports = Stateful


cli testing

cli testing

--- src/resolver.coffee
+++ src/resolver.coffee
@@ -34,8 +34,6 @@
     if process.env.NODE_PATH
       paths = paths.concat process.env.NODE_PATH.split ':'
     paths
-
-  console.log modulePaths

   # Resolve directory/npm module to index/main file.
   resolveDirectory = (path, cb) ->


add check to exclude temporary ops from packs

--- services/track-changes/app/coffee/PackManager.coffee
+++ services/track-changes/app/coffee/PackManager.coffee
@@ -263,10 +263,14 @@
 		docs = docs.slice(0,-KEEP_OPS)

 		docs.forEach (d,i) ->
+			# skip existing packs
 			if d.pack?
-				# util.log "skipping existing pack of #{d.pack.length}"
-				top = null if top? # flush the current pack
-				return # and try next
+				top = null
+				return
+			# skip temporary ops (we could pack these into temporary packs in future)
+			if d.expiresAt?
+				top = null
+				return
 			sz = BSON.calculateObjectSize(d)
 			if top?	&& top.pack.length < MAX_COUNT && top.sz + sz < MAX_SIZE
 				top.pack = top.pack.concat {v: d.v, meta: d.meta,  op: d.op, _id: d._id}
@@ -308,6 +312,7 @@
 					prev = v
 					v = p.v
 					error('bad version', v, 'in', p) if v <= prev
+					#error('expired op', p, 'in pack') if p.expiresAt?
 			else
 				prev = v
 				v = d.v


fix for editing page

fix for editing page

--- @client/edit_page.coffee
+++ @client/edit_page.coffee
@@ -386,7 +386,6 @@

                 @local.type = PAGE_TYPES.ALL
                 save @local
-                save edit_forum

             "Change to a \"Show all\" page"

@@ -404,9 +403,8 @@
             onClick: =>
               if @ordered_lists.length == 0 || confirm "Are you sure you want to convert this page? You may want to move the existing lists to a different tab first."
                 @local.type = PAGE_TYPES.ABOUT
+                @ordered_lists.splice(0, @ordered_lists.length)
                 save @local
-                @ordered_lists.splice(0, @ordered_lists.length)
-                save edit_forum()

             "Change to an \"About\" page"
           DIV
@@ -591,7 +589,6 @@

   saveIfChanged: ->
-    edit_forum = fetch 'edit_forum'
     customizations = fetch('/subdomain').customizations

     fields = ['list_sort_method', 'page_preamble']


Added 'useBaseName' (boolean) option. If 'true' template names will be the same as the basename of the file, sans prepended paths and file extensions. When coupled with globbing pattern '[root_folder]/**/*' all files matched will use their base names regardless of where the file is located in the directory tree rooted at root_folder. Note: One caveat - filenames must be unique! Otherwise name collisions will occur.

--- src/tasks/dust.coffee
+++ src/tasks/dust.coffee
@@ -74,8 +74,13 @@
 					else
 						source

-				# remove extension from template name
-				tplName = tplRelativePath.replace new RegExp( "\\#{ path.extname tplRelativePath }$" ), ""
+				tplName =
+          if options.useBaseName
+            # use basename as template name
+            path.basename tplRelativePath, path.extname tplRelativePath
+          else
+            # remove extension from template name
+            tplRelativePath.replace new RegExp( "\\#{ path.extname tplRelativePath }$" ), ""

 				try
 					output.push "// #{ tplRelativePath }\n" + dust.compile grunt.file.read( source ), tplName


Ensure (js side) jumpTo number is a valid number

Also, I haven't had my lunch/breakfast.

--- resources/assets/coffee/forum.coffee
+++ resources/assets/coffee/forum.coffee
@@ -115,6 +115,8 @@

   jumpTo: (postN) =>
     postN = parseInt postN, 10
+    return false unless isFinite(postN)
+
     postN = Math.max(postN, 1)
     postN = Math.min(postN, @totalPosts())

@@ -125,6 +127,8 @@
       @scrollTo $post.attr('data-post-id')
     else
       Turbolinks.visit @postUrlN(postN)
+
+    true

   scrollTo: (postId) =>
@@ -230,6 +234,5 @@
     e.preventDefault()
     osu.hideLoadingOverlay()

-    @jumpTo $(e.target).find('[name="n"]').val()
-
-    $.publish 'forum:topic:jumpTo'
+    if @jumpTo $(e.target).find('[name="n"]').val()
+      $.publish 'forum:topic:jumpTo'


more specs for Difference

more specs for Difference

--- spec.coffee
+++ spec.coffee
@@ -528,9 +528,24 @@
     diff = new Difference(underlying, subtrahend)

     it 'contains models from minuend', ->
+      equal diff.length, 1
       ok diff.contains(a)
-      equal diff.length, 1

     it 'does not contain models from subtrahend', ->
       ok not diff.contains(b)
       ok not diff.contains(c)
+
+    it 'updates on changes in subtrahend', ->
+      subtrahend.remove(b)
+      equal diff.length, 2
+      ok diff.contains(b)
+
+      subtrahend.add(a)
+      equal diff.length, 1
+      ok not diff.contains(a)
+
+      subtrahend.reset([d])
+      equal diff.length, 3
+      ok diff.contains(a)
+      ok diff.contains(b)
+      ok diff.contains(c)


group all add tests

group all add tests

--- src/test/jsdom.coffee
+++ src/test/jsdom.coffee
@@ -15,7 +15,6 @@
         @html = (el) => HTML(@$,el)
         callback()

-# --------------

     simple: (æ) ->
         tpl = jqueryify {@$}, new Template schema:5, ->
@@ -30,86 +29,91 @@
             ].join("")
             æ.done()

-    add: (æ) ->
-        api = new EventEmitter
-        tpl = jqueryify {@$}, new Template schema:5, ->
-            api.on('view', @$div(class:'content').add)
+    add:
+        setUp: (callback) ->
+            @api = new EventEmitter
+            @results = "no results"
+            setTimeout =>
+                @æ.equal @html(@tpl.jquery), @results
+                @æ.done()
+            , 100
+            callback()

-        setTimeout ->
-            api.emit 'view', t = new Template schema:5, ->
-                @$footer ->
-                    @$p "bar"
-            æ.equal "42#{tpl is t.xml?.parent?.builder?.template}", "42true"
-        , 42

-        setTimeout ->
-            api.emit 'view', t = new Template schema:5, ->
-                @$span ->
-                    @$p "foo"
-            æ.equal "23#{tpl is t.xml?.parent?.builder?.template}", "23true"
-        , 23
+        simple: (æ) ->
+            @æ = æ ; { $, api } = this
+            tpl = @tpl = jqueryify {$}, new Template schema:5, ->
+                api.on('view', @$div(class:'content').add)

-        setTimeout =>
-            æ.equal @html(tpl.jquery), [
+            setTimeout ->
+                api.emit 'view', t = new Template schema:5, ->
+                    @$footer ->
+                        @$p "bar"
+                æ.equal "42#{tpl is t.xml?.parent?.builder?.template}", "42true"
+            , 42
+
+            setTimeout ->
+                api.emit 'view', t = new Template schema:5, ->
+                    @$span ->
+                        @$p "foo"
+                æ.equal "23#{tpl is t.xml?.parent?.builder?.template}", "23true"
+            , 23
+
+            @results = [
                 '
'
                 "
foo
"
                 "
bar
"
                 "
"
             ].join("")
-            æ.done()
-        , 100

-    'add after end': (æ) ->
-        api = new EventEmitter
-        tpl = jqueryify {@$}, new Template schema:5, ->
-            content = @$div class:'content'
-            api.on('view', content.add)
-            content.ready(next_step)

-        next_step = ->
-            setTimeout ->
-                api.emit 'view', t = new Template schema:5, ->
-                    @$span ->
-                        @$p "foo"
-                æ.equal "16#{tpl is t.xml?.parent?.builder?.template}", "16true"
-            , 16
+        'after end': (æ) ->
+            @æ = æ ; { $, api } = this
+            tpl = @tpl = jqueryify {@$}, new Template schema:5, ->
+                content = @$div class:'content'
+                api.on('view', content.add)
+                content.ready(next_step)

-        setTimeout =>
-            æ.equal @html(tpl.jquery), [
+            next_step = ->
+                setTimeout ->
+                    api.emit 'view', t = new Template schema:5, ->
+                        @$span ->
+                            @$p "foo"
+                    æ.equal "16#{tpl is t.xml?.parent?.builder?.template}", "16true"
+                , 16
+
+            @results = [
                 '
'
                 "
foo
"
                 "
"
             ].join("")
-            æ.done()
-        , 100

-    'add to second level': (æ) ->
-        footer = null
-        api = new EventEmitter
-        tpl = jqueryify {@$}, new Template schema:5, ->
-            api.on('view', @$div(class:'content').add)
+        'to second level': (æ) ->
+            @æ = æ ; { $, api } = this
+            footer = null
+            tpl = @tpl = jqueryify {@$}, new Template schema:5, ->
+                api.on('view', @$div(class:'content').add)

-        setTimeout ->
-            api.emit 'view', footer = new Template schema:5, ->
-                api.on('footer', @$footer().add)
-            æ.equal "8#{tpl is footer.xml?.parent?.builder?.template}", "8true"
-        , 8
+            setTimeout ->
+                api.emit 'view', footer = new Template schema:5, ->
+                    api.on('footer', @$footer().add)
+                æ.equal "8#{tpl is footer.xml?.parent?.builder?.template}", "8true"
+            , 8

-        setTimeout ->
-            api.emit 'footer', t = new Template schema:5, ->
-                @$p ->
-                    @text "foo"
-                    @span "lol"
-                @$p ->
-                    @text "bar"
-                    @span "rofl"
-            æ.equal "47#{footer is t.xml?.parent?.builder?.template}", "47true"
-        , 47
+            setTimeout ->
+                api.emit 'footer', t = new Template schema:5, ->
+                    @$p ->
+                        @text "foo"
+                        @span "lol"
+                    @$p ->
+                        @text "bar"
+                        @span "rofl"
+                æ.equal "47#{footer is t.xml?.parent?.builder?.template}", "47true"
+            , 47

-        setTimeout =>
-            æ.equal @html(tpl.jquery), [
+            @results = [
                 '
'
                 ""
                 "
foolol
"
@@ -117,7 +121,5 @@
                 ""
                 "
"
             ].join("")
-            æ.done()
-        , 100


Added isFullScreen function for ELEM

Added isFullScreen function for ELEM

--- js/core/elem/elem.coffee
+++ js/core/elem/elem.coffee
@@ -440,6 +440,9 @@
   ###
   getExtraHeight: (_id)->
     @_getExtraSize( _id, 'top' ) + @_getExtraSize( _id, 'bottom' )
+
+  isFullScreen: ->
+    ( document.fullScreenElement? || document.mozFullScreen || document.webkitIsFullScreen )

   ###
   Sets delay between refreshes based on the target frame rate


new features added to demo-model

new features added to demo-model

--- src/demo-model.coffee
+++ src/demo-model.coffee
@@ -1,5 +1,23 @@

 `import * as tangojs from 'tangojs'`
+
+###*
+ * @private
+ ###
+createDeviceAttributeStruct = (name, data_format, value) ->
+  new tangojs.struct.DeviceAttribute
+    data_format: data_format
+    data_type: undefined
+    err_list: []
+    name: name
+    quality: tangojs.tango.AttrQuality.ATTR_VALID
+    r_dim: null
+    time: new tangojs.tango.TimeVal
+      tv_sec: parseInt(new Date().getTime()/1000)
+      tv_usec: 0
+      tv_nsec: 0
+    value: value
+    w_dim: null

 ###*
  * @private
@@ -39,19 +57,7 @@
     standard_unit: unit
     writable: writable
     writable_attr_name: undefined
-  get_value: -> new tangojs.struct.DeviceAttribute
-    data_format: data_format
-    data_type: undefined
-    err_list: []
-    name: name
-    quality: tangojs.tango.AttrQuality.ATTR_VALID
-    r_dim: null
-    time: new tangojs.tango.TimeVal
-      tv_sec: parseInt(new Date().getTime()/1000)
-      tv_usec: 0
-      tv_nsec: 0
-    value: _value
-    w_dim: null
+  get_value: -> createDeviceAttributeStruct(name, data_format, _value)
   set_value: (v) ->
     _value = v

@@ -76,8 +82,10 @@
  ###
 createDevice = (name) ->

+  _state = tangojs.tango.DevState.OFF
+
   get_status: -> 'ON'
-  get_state: -> tangojs.tango.DevState.ON
+  get_state: -> _state

   get_info: -> new tangojs.struct.DeviceInfo
     exported: true
@@ -90,7 +98,7 @@
     boolean: new tangojs.struct.DbDatum('boolean', true)

   attributes:
-    scalar: createAttribute(0
+    scalar: createAttribute(0,
                             'scalar',
                             'u',
                             tangojs.tango.AttrDataFormat.SCALAR,
@@ -103,7 +111,7 @@
                               max_warning: '50'
                               max_alarm: '70'
                             }))
-    string: createAttribute(undefined
+    string: createAttribute(undefined,
                             'string'
                             undefined,
                             tangojs.tango.AttrDataFormat.SCALAR,
@@ -113,14 +121,64 @@
                             undefined,
                             undefined,
                             null)
+    sine_trend: (->
+      name = 'sine_trend'
+      format = 'scientific,setprecision(3)'
+      attr = createAttribute(0,
+                             name,
+                             'u',
+                             tangojs.tango.AttrDataFormat.SCALAR,
+                             format,
+                             tangojs.tango.DispLevel.OPERATOR,
+                             tangojs.tango.AttrWriteType.READ,
+                             undefined,
+                             undefined,
+                             null)
+      attr.get_value = ->
+        value = Math.sin((new Date()).getTime()/1000.0)
+        createDeviceAttributeStruct(name, format, value)
+      attr
+    )()
+    boolean: createAttribute(false,
+                             'boolean'
+                             undefined,
+                             tangojs.tango.AttrDataFormat.SCALAR,
+                             undefined,
+                             tangojs.tango.DispLevel.OPERATOR,
+                             tangojs.tango.AttrWriteType.READ_WRITE,
+                             undefined,
+                             undefined,
+                             null)
+    spectrum: createAttribute(([1..10].map (_i) -> Math.random()),
+                              'spectrum'
+                              undefined,
+                              tangojs.tango.AttrDataFormat.SPECTRUM,
+                              undefined,
+                              tangojs.tango.DispLevel.OPERATOR,
+                              tangojs.tango.AttrWriteType.READ,
+                              undefined,
+                              undefined,
+                              null)

   commands:
-    double: createCommand(((x) -> 2*x),
-                          'double',
-                          tangojs.tango.DispLevel.OPERATOR)
+    double_arg: createCommand(((x) -> 2*x),
+                              'double',
+                              tangojs.tango.DispLevel.OPERATOR)
     to_upper: createCommand(((x) -> x.toUpperCase()),
-                          'to_upper',
-                          tangojs.tango.DispLevel.OPERATOR)
+                            'to_upper',
+                            tangojs.tango.DispLevel.OPERATOR)
+    goto_on: createCommand((-> _state = tangojs.tango.DevState.ON),
+                           'goto_on',
+                           tangojs.tango.DispLevel.OPERATOR)
+    goto_off: createCommand((-> _state = tangojs.tango.DevState.OFF),
+                            'goto_off',
+                            tangojs.tango.DispLevel.OPERATOR)
+    goto_fault: createCommand((-> _state = tangojs.tango.DevState.FAULT),
+                              'goto_fault',
+                              tangojs.tango.DispLevel.OPERATOR)
+    goto_alarm: createCommand((-> _state = tangojs.tango.DevState.OFF),
+                              'goto_off',
+                              tangojs.tango.DispLevel.OPERATOR)

 ###*
  * @private


example model with data_type in attributes and commands

--- src/demo-model.coffee
+++ src/demo-model.coffee
@@ -31,13 +31,14 @@
                    writable,
                    min_value,
                    max_value,
-                   alarm
+                   alarm,
+                   data_type
                    ) ->
   _value = initial
   get_info: -> new tangojs.struct.AttributeInfo
     att_alarm: alarm
     data_format: data_format
-    data_type: undefined
+    data_type: data_type
     description: name
     display_unit: unit
     enum_labels: []
@@ -66,14 +67,16 @@
  ###
 createCommand = (handler,
                  name,
-                 level) ->
+                 level,
+                 in_type,
+                 out_type) ->
   get_info: -> new tangojs.struct.CommandInfo
     cmd_name: name
     cmd_tag: 0
-    in_type: undefined
+    in_type: in_type
     in_type_desc: 'command input'
     level: level
-    out_type: undefined
+    out_type: out_type
     out_type_desc: 'command output'
   inout: (devData) -> new tangojs.struct.DeviceData(handler(devData.value))

@@ -111,7 +114,8 @@
                             new tangojs.tango.AttributeAlarm({
                               max_warning: '50'
                               max_alarm: '70'
-                            }))
+                            }),
+                            tangojs.tango.AttributeDataType.ATT_DOUBLE)
     string: createAttribute(undefined,
                             'string'
                             undefined,
@@ -121,7 +125,8 @@
                             tangojs.tango.AttrWriteType.READ_WRITE,
                             undefined,
                             undefined,
-                            null)
+                            null,
+                            tangojs.tango.AttributeDataType.ATT_STRING)
     sine_trend: (->
       name = 'sine_trend'
       format = 'scientific,setprecision(3)'
@@ -134,7 +139,8 @@
                              tangojs.tango.AttrWriteType.READ,
                              undefined,
                              undefined,
-                             null)
+                             null,
+                             tangojs.tango.AttributeDataType.ATT_DOUBLE)
       attr.get_value = ->
         value = Math.sin((new Date()).getTime()/1000.0)
         createDeviceAttributeStruct(name, format, value)
@@ -149,7 +155,8 @@
                              tangojs.tango.AttrWriteType.READ_WRITE,
                              undefined,
                              undefined,
-                             null)
+                             null,
+                             tangojs.tango.AttributeDataType.ATT_BOOL)
     spectrum: createAttribute(([1..10].map (_i) -> Math.random()),
                               'spectrum'
                               undefined,
@@ -159,27 +166,40 @@
                               tangojs.tango.AttrWriteType.READ,
                               undefined,
                               undefined,
-                              null)
+                              null,
+                              tangojs.tango.AttributeDataType.ATT_DOUBLE)

   commands:
     double_arg: createCommand(((x) -> 2*x),
                               'double',
-                              tangojs.tango.DispLevel.OPERATOR)
+                              tangojs.tango.DispLevel.OPERATOR,
+                              tangojs.tango.AttributeDataType.ATT_DOUBLE,
+                              tangojs.tango.AttributeDataType.ATT_DOUBLE)
     to_upper: createCommand(((x) -> x.toUpperCase()),
                             'to_upper',
-                            tangojs.tango.DispLevel.OPERATOR)
+                            tangojs.tango.DispLevel.OPERATOR,
+                            tangojs.tango.AttributeDataType.ATT_NO_DATA,
+                            tangojs.tango.AttributeDataType.ATT_NO_DATA)
     goto_on: createCommand((-> _state = tangojs.tango.DevState.ON),
                            'goto_on',
-                           tangojs.tango.DispLevel.OPERATOR)
+                           tangojs.tango.DispLevel.OPERATOR,
+                           tangojs.tango.AttributeDataType.ATT_NO_DATA,
+                           tangojs.tango.AttributeDataType.ATT_NO_DATA)
     goto_off: createCommand((-> _state = tangojs.tango.DevState.OFF),
                             'goto_off',
-                            tangojs.tango.DispLevel.OPERATOR)
+                            tangojs.tango.DispLevel.OPERATOR,
+                            tangojs.tango.AttributeDataType.ATT_NO_DATA,
+                            tangojs.tango.AttributeDataType.ATT_NO_DATA)
     goto_fault: createCommand((-> _state = tangojs.tango.DevState.FAULT),
                               'goto_fault',
-                              tangojs.tango.DispLevel.OPERATOR)
+                              tangojs.tango.DispLevel.OPERATOR,
+                              tangojs.tango.AttributeDataType.ATT_NO_DATA,
+                              tangojs.tango.AttributeDataType.ATT_NO_DATA)
     goto_alarm: createCommand((-> _state = tangojs.tango.DevState.ALARM),
                               'goto_off',
-                              tangojs.tango.DispLevel.OPERATOR)
+                              tangojs.tango.DispLevel.OPERATOR,
+                              tangojs.tango.AttributeDataType.ATT_NO_DATA,
+                              tangojs.tango.AttributeDataType.ATT_NO_DATA)

 ###*
  * @private


try again to fixup getRoot()

try again to fixup getRoot()

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -83,38 +83,67 @@
   getRoot: (altPackageName) ->

     # finding the root is even more complicated in the new Build API, yay.
-    # 1. `meteor test-packages ./` gives a package name but root is ''
-    # 2. `meteor run` gives:
-    #       a package name for a package file and root is packages/[username:]packageName
-    #       null for package name for an app file and root is ''
-
-    # first, if we're testing a package, then the root is ''
-    if 'test-packages' in process.argv then return ''
-
-    # second, if there is a cached value then use it
+    # let's try yet another tactic to overcome the API's missing directory information
+
+    # where are the npm modules?
+    # A. app file in a running app
+    #   1. packages/npm-container/.npm/package/node_modules
+    #   2. node_modules (if someone installs them in the app manually)
+    # B. package file in a running app
+    #   1. packages/packageName/.npm/package/node_modules
+    #   2. packages/username:packageName/.npm/package/node_modules
+    #   3. ~/.meteor/packages/username_packageName/version/npm/node_modules
+    # C. package file in a package being tested via `meteor test-packages`
+    #   1. .npm/package/node_modules - when called like `meteor test-packages ./`
+    #   2. otherwise the root is where the referenced packages are, which can be anything
+
+    # if there is a cached value then use it
     if this.___root? then return this.___root

-    # third, use the package name to determine if we're processing an app or package file
+    # use the package name to determine if we're processing an app or package file
     # null means app file
-    root = @getPackageName()
-
-    if root?
-      # fourth, allow package directory name to optionally have `username:`
+    packageName = @getPackageName()
+
+    if packageName?
+      # allow package directory name to optionally have `username:`
       # if there is a deeply hidden property containing keys with the actual directory name...
       if this?._resourceSlot?.packageSourceBatch?.unibuild?.watchSet?.files?
+
         # get one of the keys
         break for watchedFile of this._resourceSlot.packageSourceBatch.unibuild.watchSet.files
-        # get path relative to the app's directory
+
+        # get path relative to the current workind directory
         relativePath = path.relative (path.resolve '.'), watchedFile
-        # strip off tail, only keep the 'packages/packageDirectory' portion
-        root = relativePath[...relativePath.indexOf('/', 10)]
+
+        # the easy one, it's a file in a package in an app
+        if 'packages/' is relativePath[...9]
+          # strip off tail, only keep the 'packages/packageDirectory' portion
+          root = relativePath[...relativePath.indexOf('/', 10)]
+
+        # TODO: can't test this with `meteor test-packages` until it's published... great
+        # TODO: can't test this in ~/.meteor/packages until i publish a package using it... great
+        else # peel off path parts until we find either .npm or npm
+          # i dislike doing `fs` calls, but, it seems there is no other option
+          # it's a file at first...
+          dir = relativePath
+          # dirname '' = '.'
+          until root? or dir is '.'
+            # get the next directory up the path
+            dir = path.dirname dir
+            # try both:
+            # unpublished: .npm/package
+            # published  : npm
+            for npm in [ '.npm/package', 'npm' ]
+              if fs.existsSync path.join dir, npm then root = dir ; break
+          # root wasn't found then ...
+          root ?= ''
         # cache this value so we don't have to calculate it again
         this.___root = root
       # fifth, use the 'meteor way' of having a package's directory be its name
       # without the username
       else
-        index = root.indexOf(':') + 1
-        root = 'packages/' + root[index...]
+        index = packageName.indexOf(':') + 1
+        root = 'packages/' + packageName[index...]

     # sixth, use an alternate package name (for npm-container...)
     else if altPackageName?


Clean the build before loading another route

--- js/app.coffee
+++ js/app.coffee
@@ -123,6 +123,10 @@
           "&filters%5B0%5D%5Bcomparison%5D=contains&filters%5B0%5D%5Bvalue%5D=" +
           path[1]

+  cleanBuild: () =>
+    if @build() then @build().clean()
+    @build(null)
+
   refreshBuildState: () =>
     VM.loadProjects()
     VM.current_page().refresh()
@@ -248,8 +252,7 @@
     @build_has_been_loaded(false)
     project_name = "#{username}/#{project}"
     @maybeLoadProjectDetails(project_name)
-    @build().clean() if @build()
-    @build(null)
+    @cleanBuild()
     $.getJSON "/api/v1/project/#{project_name}/#{build_num}", (data) =>
       @build(new CI.inner.Build data)
       @build_has_been_loaded(true)
@@ -361,6 +364,7 @@
 window.SammyApp = Sammy 'body', (n) ->
   @bind 'run-route', (e, data) ->
     VM.clearErrorMessage()
+    VM.cleanBuild()
     mixpanel.track_pageview(data.path)
     if window._gaq? # we dont use ga in test mode
       window._gaq.push data.path


notes on #175

notes on #175

--- app/graph.coffee
+++ app/graph.coffee
@@ -25,6 +25,7 @@
     render: ->
       React.DOM.div {className: 'graph'}
     componentDidMount: ->
+      # FIXME #175 props can change
       node = @getDOMNode()
       @props.model.onValue ({data, params}) ->
         node.removeChild(node.lastChild) while node.hasChildNodes()


s3 sorts alphabetically

s3 sorts alphabetically

--- chrome/content/zotero-better-bibtex/errorReport.coffee
+++ chrome/content/zotero-better-bibtex/errorReport.coffee
@@ -25,7 +25,7 @@

   init: ->
     Zotero.debug('BBT.error.init')
-    @key = "#{Zotero.Utilities.generateObjectKey()}-#{Zotero.Utilities.generateObjectKey()}"
+    @key = "#{(new Date()).toISOString().replace(/T.*/, '').replace(/-/g, '')}-#{Zotero.Utilities.generateObjectKey()}"
     Zotero.debug("BBT.error.init: #{@key}")

     wizard = document.getElementById('zotero-error-report')


change type to typeOf for coffee file

change type to typeOf for coffee file

to be a little more inline with `typeof` I decided to use `typeOf`, the difference is the `O` is capitalised, and native `typeof` is NOT.

--- type.coffee
+++ type.coffee
@@ -5,11 +5,11 @@
 Person = ->
     return "Tom"

-### class type START ###
-class type
+### class typeOf START ###
+class typeOf
   constructor: (@value) ->
     if @value == null then return "[object Null]"
-    if @value == undefined or typeof @value == "undefined" then return "[object Undefined]"
+    if @value == undefined or typeOf @value == "undefined" then return "[object Undefined]"
     getClass =  toClass(@value)
     if getClass == "[object annonymous]" then return @value
     return getClass
@@ -30,7 +30,7 @@
       else str
   )

-### class type END ###
+### class typeOf END ###

 list = [
   ""
@@ -50,8 +50,8 @@
   {}
   new Object()
   ->
-  type("")
-  new type()
+  typeOf("")
+  new typeOf()
   new Date()
   new Animal("Ben")
   new Animal()
@@ -65,7 +65,7 @@

 ####
 for item in list
-  console.log(type(item) , "\t\t\t :: \t\t\t", item )
+  console.log(typeOf(item) , "\t\t\t :: \t\t\t", item )
 ###

-console.log type Animal()
+console.log typeOf Animal()


Fix specs for scrolling after render()

Fix specs for scrolling after render()

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -2417,17 +2417,19 @@

         describe 'when the server responds with an error code', ->

+          mockReveal()
+
           it 'ignores the { scroll } option', asyncSpec (next) ->
             fixture('.target', text: 'target text')
             fixture('.other', text: 'other text')
             fixture('.fail-target', text: 'fail-target text')
-            up.render('.target', url: '/path', failTarget: '.fail-target', scroll: '.other', failScroll: 'target')
+            up.render('.target', url: '/path', failTarget: '.fail-target', scroll: '.other')

             next =>
               @respondWithSelector('.fail-target', status: 500, text: 'new fail-target text')

             next =>
-              expect(@revealedText).toEqual ['new fail-target text']
+              expect(@revealMock).not.toHaveBeenCalled()

           it 'accepts a { failScroll } option for error responses', asyncSpec (next) ->
             fixture('.target', text: 'old target text')


add comment

add comment

--- oretamaBot/scripts/twitter-search.coffee
+++ oretamaBot/scripts/twitter-search.coffee
@@ -40,6 +40,8 @@
     query = msg.match[1]
     twit = getTwit()

+    # TODO: ２つ以上のツイートを同時に流すことができない
+    # TODO: アンド検索ができない
     stream.stop() if stream
     stream = twit.stream('statuses/filter', { track: query,language: 'ja' })


Group: home: feedcoverphoto to update realtime and some ux improvements

--- client/Social/Activity/views/feedcoverphotoview.coffee
+++ client/Social/Activity/views/feedcoverphotoview.coffee
@@ -1,24 +1,60 @@
 class FeedCoverPhotoView extends KDView
+
+  _notify = (msg)-> new KDNotificationView title : msg or 'There was an error, please try again later!'
+
   constructor: (options = {}, data) ->
     options.cssClass = "group-cover-view"
     super options, data

     {groupsController} = KD.singletons
+
     groupsController.ready =>
       @group = groupsController.getCurrentGroup()

-      if @group.slug isnt "koding"
-        container = new KDView
-          cssClass : "container"
-          size     :
-            height : 315
+      @decorateHeader()  if @group.slug isnt "koding"

-        container.addSubView @getCoverUpdateButton()
-        container.addSubView @getCoverView()
-      else
-        container = new KDCustomHTMLView
+      @group.on "update", @bound "decorateHeader"

-      @addSubView container
+  decorateHeader:->
+
+    unless @container
+      @container = new KDView
+        cssClass : "container"
+        size     : height : 315
+
+      @coverView = new KDCustomHTMLView
+        tagName    : 'figure'
+        attributes :
+          style    : "background-image: url(#{@group.customize?.coverPhoto});"
+
+      @listController = new KDListViewController
+        startWithLazyLoader : no
+        view                : new KDListView
+          type              : "collage"
+          cssClass          : "cover-list"
+          itemClass         : CollageItemList
+
+
+      @container.addSubView @getCoverUpdateButton()
+      @container.addSubView @coverView
+      @container.addSubView @listController.getView()
+      @addSubView @container
+
+    @toggle()
+
+  toggle:->
+
+    if @group.customize?.coverPhoto
+      @listController.getView().hide()
+      @coverView.setCss 'background-image', "url(#{@group.customize.coverPhoto})"
+      @coverView.show()
+    else
+      @coverView.hide()
+      @listController.getView().show()
+      @listController.removeAllItems()
+      @group.fetchMembersFromGraph limit : 20, (err, accounts) =>
+        @listController.instantiateListItems accounts
+

   getCoverUpdateButton: ->
     if "admin" in KD.config.roles
@@ -28,39 +64,35 @@
         type      : "submit"
         title     : "Update cover image"
         callback  : =>
-          new UploadImageModalView
-            cssClass   : "group-cover-uploader"
-            title      : "Change Cover Photo"
-            width      : 1004
-            image      :
-              type     : "coverPhoto"
-              size     :
-                width  : 914
-                height : 315
+          modal = new UploadImageModalView
+            cssClass      : "group-cover-uploader"
+            title         : "Change Cover Photo"
+            uploaderTitle : "Drag & drop image here! <small>Cover photos are 948 pixels wide and 315 pixels tall.</small>"
+            width         : 1004
+            image         :
+              type        : "coverPhoto"
+              size        :
+                width     : 914
+                height    : 315
+            buttons       :
+              uploadButton:
+                title     : "Upload"
+                cssClass  : "modal-clean-green"
+                callback  : =>
+                  modal.upload (err)=>
+                    return  if err then _notify()
+                    modal.destroy()

+              clear       :
+                title     : "Remove cover photo"
+                cssClass  : "modal-clean-red"
+                callback  : =>
+                  @group.modify "customize.coverPhoto" : "", (err)=>
+                    return _notify()  if err
+                    modal.destroy()
     else
       new KDCustomHTMLView

-  getCoverView: ->
-    if @group.customize?.coverPhoto
-      new KDCustomHTMLView
-        tagName     : 'figure'
-        attributes  :
-          style     : "background:url(#{@group.customize?.coverPhoto})"
-    else
-      # if group doesnt has cover photo, put collage of group users
-      collageList           = new KDListViewController
-        startWithLazyLoader : no
-        view                : new KDListView
-          type              : "collage"
-          cssClass          : "cover-list"
-          itemClass         : CollageItemList
-
-      options = limit : 20
-
-      @group.fetchMembersFromGraph options, (err, accounts) ->
-        collageList.instantiateListItems accounts
-      collageList.getView()

 class CollageItemList extends KDListItemView
   constructor: (options = {}, data) ->


profile: fix for staticnavlinks not working after refreshActivities change

--- client/app/MainApp/lazy/staticprofilecontroller.coffee
+++ client/app/MainApp/lazy/staticprofilecontroller.coffee
@@ -499,7 +499,8 @@
         @setClass 'blocked'

   click :->
-    @getDelegate().emit 'StaticProfileNavLinkClicked', @getDomId()
+    @getDelegate().emit 'StaticProfileNavLinkClicked', @getDomId(), @getDelegate().activityListWrapper, @getDelegate().activityController, =>
+

 class StaticNavCheckBox extends KDInputView


Stop click event

Stop click event

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -86,8 +86,9 @@
     @settingsIcon = new KDCustomHTMLView
       tagName     : 'span'
       click       : (e) =>
-        if @machine.isMine() then @handleMachineSettingsClick e
-        else if @machine.isApproved() then @showSidebarSharePopup {}, e
+        kd.utils.stopDOMEvent e
+        if @machine.isMine() then @handleMachineSettingsClick
+        else if @machine.isApproved() then @showSidebarSharePopup {}

   createSettingsIconPlaceholder: ->
@@ -113,11 +114,9 @@
     return state in [ NotInitialized, Running, Stopped, Terminated, Unknown ]

-  handleMachineSettingsClick: (event) ->
+  handleMachineSettingsClick: () ->

     return  if not @settingsEnabled()
-
-    kd.utils.stopDOMEvent event

     new MachineSettingsModal {}, @machine

@@ -171,9 +170,7 @@
     return data.workspaces.first?.channelId

-  showSidebarSharePopup: (options = {}, event) ->
-
-    kd.utils.stopDOMEvent event
+  showSidebarSharePopup: (options = {}) ->

     options.position  = @getPopupPosition 20
     options.channelId = @getChannelId @getData()


Maintabs: closing a tab pruning the others' content fixed

--- client/landing/app/MainApp/maintabs/maintabview.coffee
+++ client/landing/app/MainApp/maintabs/maintabview.coffee
@@ -112,7 +112,12 @@

     options.cssClass = @utils.curryCssClass "content-area-pane", options.cssClass
     options.class  or= KDView
-    options.domId    = "maintabpane-#{@utils.slugify options.name}"
+
+    # this is a temporary hack
+    # for reviving the main tabs
+    # a better solution tbdl - SY
+    options.domId  = "maintabpane-#{@utils.slugify options.name}"
+    options.domId += "-#{@utils.getRandomNumber()}" if document.getElementById options.domId

     paneInstance = new MainTabPane options


feat: POC cids

feat: POC cids

--- client/src/daily-report/services/report-builder.coffee
+++ client/src/daily-report/services/report-builder.coffee
@@ -15,13 +15,23 @@
     .then (bdcBase64) ->
       src = if useCid then 'cid:bdc' else bdcBase64

-      message.body = message.body.replace '{bdc}', "<img src='#{src}' />"
+      base64bis = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAIAAAD6CAYAAAA7ruXSAAAgAElEQVR4Xu2dCbxd49X/VyKDDBISEtWKlwqtmHVSipqqVUVrVoqqWSSoEsQQMUVCYkpRs5hLhQ6m11S0WqkSWqmhMdMbCRkkuUn+Z+2+5/5Pbu6593n2Xvuufc79ns+nn7a5a63n2d/13L3W8zvP3rfTktJH+EAAAhCAAAQgAAEIQAACEIAABCDQIQh0QgjoEHnmIiEAAQhAAAIQgAAEIAABCEAAAgkBhAAWAgQgAAEIQAACEIAABCAAAQhAoAMRQAjoQMnmUiEAAQhAAAIQgAAEIAABCEAAAggBrAEIQAACEIAABCAAAQhAAAIQgEAHIoAQ0IGSzaVCAAIQgAAEIAABCEAAAhCAAAQQAlgDEIAABCAAAQhAAAIQgAAEIACBDkQAIaADJZtLhQAEIAABCEAAAhCAAAQgAAEIIASwBiAAAQhAAAIQgAAEIAABCEAAAh2IAEJAB0o2lwoBCEAAAhCAAAQgAAEIQAACEEAIYA1AAAIQgAAEIAABCEAAAhCAAAQ6EAGEAKdkz5gxQ+bOnStf+MIXnGaw9LCNjY3JfPr06ZNqPvPnz5cPPvhABg0alMq/Vp3efvtt6dWrl6y00kq1egnMGwIQgAAEIAABCEAAAhDoYAQQApwSftddd8nChQtl3333dZnBggULpFu3bk1jZ53P3/72N3nggQfk1FNPTXU9zeeTKoiD0+jRo2WnnXaSzTbbzGF0hoQABCAAAQhAAAIQgAAEIBBPIBch4LTTTpP//Oc/0rlzZ+nXr5989atflR/84AfSqVOn+BnWqUfWjXcWLH/5y1/k8ccflxNOOKEQQkBL82l+fYsXL5Ybb7xRPvvsMzniiCOafvzee+/JtddeK++++66sv/76cvDBB8vyyy+fBU+UL0JAFC6MIQABCEAAAhCAAAQgAIECEMhFCDj66KPluOOOk8997nPy73//W2644QbZdttt5bvf/W4BLrkYU/AUAv70pz/Jk08+KSeeeGIhhICW5lOZpenTp8vtt98u77zzjnzpS19qEgKWLFkiZ599tqy33nrJ+tJ1pmuuPU9ZIAQU4/eJWUAAAhCAAAQgAAEIQAAC4QRyEwJOPvlkWX311ZOZ6Cbu448/TjZwzTd9jzzyiEybNq1pc/fQQw/JH/7wh+Sb3xVXXFF++MMfyqabbiqvvPKKTJo0SRoaGmTAgAFyxhlnLHXCoCw46EmErl27ygYbbCB77bWX9OzZM5nDlClT5J577pFPP/00iaebxS5dusif//xnuf/++5O4enph1KhRTfR0o3zIIYckG039+emnny4TJkxI/J544gn561//KsOHD0+erb/11lvlxRdfTOa83377yTrrrNPquJVCgDL5zW9+IyNGjBA9Iv+rX/1K3nzzzWTuw4YNk89//vNLZfTDDz+Um2++ORFZ9Gc6nr5rQJ/zHzt2rOjP582bl/zboYcemvCq/Oh4+i16+XPuuefK//7v/8qzzz6bjK8nOXbYYQfZeeedE5PWrq8co/mjAVdddVXCo3v37rLlllvKbrvtlpiW86vz+8Y3viEHHHBAsiaaz6d///5N89PYmjddE6+99lrTWtHn83Xul1xySfKYwz//+U+58sor5eKLL25aG3piQMWC8ePHJzZPP/108p+yCHL11VfLaqutllyrniq45ZZb5K233pI111xTDjrooOTZfxUc9LEHPUWhbL7zne8kwoN+KoWAm266ST755BM56qij5B//+Eer6zX8VxRLCEAAAhCAAAQgAAEIQAACtgRyFwJmzZqVbMw233zzZAPVmhCgm/I77rgj2byuuuqqohurwYMHJ89g68ZfN19f+9rXElFBN2+Vn7///e+Jr27MdZN52223Se/evZNN4/vvv59s2A477LDE7/LLL5dvfetb8u1vf1t0Q67xVDTQF95Vbpp1M6sbbd0k6tx0g/6LX/xC1lprreTb55VXXjn5mW5idcz9999fdNP629/+NtmgqijR2rj6jgDdDOt8dN46looduhnfc889Zfbs2clGtPJZ/kWLFslZZ50lG264oWy99dbJvHSDes455yQb1qFDhybP6ffo0UPuu+8+mTNnTvJvzYUAPRGgY+pnueWWSzh89NFHsvfeeyf/fdlll8nxxx+fbIirXZ8KItWEAN2AK3/dXKt4otz0Wk466SQ55ZRTEnZ6fcq7vCYq59PSMm8uGum16wZdeehH4+njDueff/5SL+/TfzvyyCNl7bXXTgQUHU+FAd3UKyvd8Ot1nnnmmUk+ttpqq4SH5kHXzx//+Mckp8ccc0wiiqivXo/mqywE6Dp/6qmnkn9X8aOt9Wr7a0w0CEAAAhCAAAQgAAEIQAAC4QRyEwJ0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'
+
+      srcBis = if useCid then 'cid:image' else base64bis
+
+      message.body = message.body.replace '{bdc}', "<img src='#{src}' /><img src='#{srcBis}' />"
       if useCid
         message.cids = [ {
           type: 'image/png'
           name: 'BDC'
           base64: bdcBase64.split(',')[1]
           id: 'bdc'
+        },
+        {
+          type: 'image/png'
+          name: 'Image'
+          base64: base64bis.split(',')[1]
+          id: 'image'
         } ]
       message


Show a circle near the year when the user has holydays to spend

--- clients/antranet/app/coffee/directives/holidays.coffee
+++ clients/antranet/app/coffee/directives/holidays.coffee
@@ -15,6 +15,7 @@
                         id: item.id
                         y: counter
                         selected: scope.year.id == item.id
+                        color: if item.consumed_days + item.requested_days < item.total_days then "#a2bf2f" else "#ffffff"
                     }

                 data = [
@@ -96,6 +97,16 @@
                     .attr('x', 0)
                     .attr('y', (d) -> return d.y)
                     .text((d) -> return d.year)
+
+                svg.select('g#g2').selectAll("circle")
+                    .data(years)
+                    .enter()
+                    .append('circle')
+                    .attr('fill', (d) -> return d.color)
+                    .attr('r', (d) -> return if d.selected then 6 else 5)
+                    .attr('cx', (d) -> return if d.selected then 54 else 50)
+                    .attr('cy', (d) -> return d.y - 6)
+                    .attr('stroke', 'white')

                 element.on('click', 'svg g#g2 text', (event) ->
                     target = angular.element(event.currentTarget)


Add subscribeGraph option to createNetwork to choose which implementation to utilize

--- src/lib/NoFlo.coffee
+++ src/lib/NoFlo.coffee
@@ -27,7 +27,9 @@
 #
 # [Network](../Network/) is used for running NoFlo graphs. The direct Network inteface is only provided
 # for backwards compatibility purposes. Use `createNetwork` instead.
+Network = require('./Network').Network
 exports.Network = require('./LegacyNetwork').Network
+{ deprecated } = require './Platform'

 # ### Platform detection
 #
@@ -95,15 +97,28 @@
 #         console.log('Network is now running!');
 #       })
 #     }, true);
-exports.createNetwork = (graph, callback, options) ->
-  unless typeof options is 'object'
+exports.createNetwork = (graph, options, callback) ->
+  if typeof options is 'function'
+    opts = callback
+    callback = options
+    options = opts
+  if typeof options is 'boolean'
     options =
       delay: options
+  unless typeof options is 'object'
+    options = {}
+  if typeof options.subscribeGraph is 'undefined'
+    # Default to legacy network for backwards compatibility.
+    options.subscribeGraph = true
   unless typeof callback is 'function'
+    deprecated 'Calling noflo.createNetwork without a callback is deprecated'
     callback = (err) ->
       throw err if err

-  network = new exports.Network graph, options
+  # Choose legacy or modern network based on whether graph
+  # subscription is needed
+  NetworkType = if options.subscribeGraph then Network else exports.Network
+  network = new NetworkType graph, options

   networkReady = (network) ->
     # Send IIPs
@@ -114,13 +129,14 @@
   # Ensure components are loaded before continuing
   network.loader.listComponents (err) ->
     return callback err if err
-    # Empty network, no need to connect it up
-    return networkReady network if graph.nodes.length is 0

     # In case of delayed execution we don't wire it up
     if options.delay
       callback null, network
       return
+
+    # Empty network, no need to connect it up
+    return networkReady network if graph.nodes.length is 0

     # Wire the network up and start execution
     network.connect (err) ->


[DOC] Add documentation for src/frontend/event.coffee

--- src/frontend/event.coffee
+++ src/frontend/event.coffee
@@ -15,16 +15,19 @@

 ###

+# The event manager contain callbacks array
 class   EventManager
     constructor         : () ->
         @events = {}

+    # Get a callback from the array with a string
     getEvent            : (eventName) =>
         for key, val of @events
             if eventName == key
                 return val
         return null

+    # Add a new callback into the array with a string as a key
     addEvent            : (eventName, event) =>
         @events[eventName] = event


Some more refactoring

Some more refactoring

--- src/translator.coffee
+++ src/translator.coffee
@@ -65,10 +65,15 @@
 # ###################################################################################################

 do ()->
-  literally_translated_bin_op_list = "+ - * / % += -= *= /= %= = == != < <= > >=".split ' '
   holder = new bin_op_translator_holder
-  for v in literally_translated_bin_op_list
-    holder.op_list[v]  = new bin_op_translator_framework "($1$op$2)"
+  for v in "+ - * / % += -= *= /= %= = == != < <= > >=".split ' '
+    holder.op_list[v]     = new bin_op_translator_framework "($1$op$2)"
+  holder.op_list["**"]  = new bin_op_translator_framework "Math.pow($1, $2)"
+  holder.op_list["//"]  = new bin_op_translator_framework "Math.floor($1 / $2)"
+  holder.op_list["%%"]  = new bin_op_translator_framework "(function(a, b){return (a % b + b) % b})($1, $2)"
+  holder.op_list["**="] = new bin_op_translator_framework "$1 = Math.pow($1, $2)"
+  holder.op_list["//="] = new bin_op_translator_framework "$1 = Math.floor($1 / $2)"
+  holder.op_list["%%="] = new bin_op_translator_framework "$1 = (function(a, b){return (a % b + b) % b})($1, $2)"

   trans.translator_hash['bin_op'] = translate:(ctx, node)->
     op = node.value_array[1].value_view
@@ -76,25 +81,13 @@
     node.value_array[1].value = node.value_array[1].value_view

     # return is implied, I actually mean it. This if block is intended to be the last statement in the function.
-    if op in literally_translated_bin_op_list
+    if op of holder.op_list
       holder.translate ctx, node
     else
       [a,_skip,b] = node.value_array
       a_tr = ctx.translate a
       b_tr = ctx.translate b
       switch op
-        when "**"
-          "Math.pow(#{a_tr}, #{b_tr})"
-        when "//"
-          "Math.floor(#{a_tr} / #{b_tr})"
-        when "%%"
-          "(function(a, b){return (a % b + b) % b})(#{a_tr}, #{b_tr})"
-        when "**="
-          "#{a_tr} = Math.pow(#{a_tr}, #{b_tr})"
-        when "//="
-          "#{a_tr} = Math.floor(#{a_tr} / #{b_tr})"
-        when "%%="
-          "#{a_tr} = (function(a, b){return (a % b + b) % b})(#{a_tr}, #{b_tr})"
         when "and"
           if a.mx_hash.type.toString() == "int"   # type inference ensures the second operand to be int
             "(#{a_tr}&#{b_tr})"
@@ -132,7 +125,7 @@

     # Here is some old code (maybe del later):
-
+
     # if op in ['or', 'and']
     #   # needs type inference
     #   [a,_skip,b] = node.value_array


Print warning for compiled files w/o joinTo match

--- src/fs_utils/write.coffee
+++ src/fs_utils/write.coffee
@@ -30,6 +30,8 @@
     unless paths.length
       if file.error
         logger.error formatError file
+      if file.data
+        logger.warn "'#{file.path}' compiled, but not written. Check your #{file.type}.joinTo config."

   Object.keys(map).map (generatedFilePath) ->
     sourceFiles = map[generatedFilePath]


S63565: Removing kludge needed to keep tests happy while changing appsdk

--- test/spec/roadmapplanningboard/PlanningGridBoardSpec.coffee
+++ test/spec/roadmapplanningboard/PlanningGridBoardSpec.coffee
@@ -64,8 +64,7 @@

   it 'should have a add new button', ->
     @createGridboard().then =>
-      # kludge to get appsdk changes in without breaking this test
-      button = @gridboard.down('#expandButton') || @gridboard.down('#new')
+      button = @gridboard.down('#expandButton')
       expect(button).toBeDefined()

   describe 'integration', ->


Removed unused class.

Removed unused class.

--- core/app/backbone/models/auto_completed_channel.coffee
+++ core/app/backbone/models/auto_completed_channel.coffee
@@ -1 +0,0 @@
-class window.AutoCompletedChannel extends Backbone.Model


Clean up keyword scene implementation

Clean up keyword scene implementation

--- source/javascripts/stagehand.js.coffee
+++ source/javascripts/stagehand.js.coffee
@@ -25,8 +25,7 @@
 class Stage
   constructor: (@name) ->
     @scenes = {}
-    @keyword_scenes =
-      all: false
+    @keyword_scenes = {}
     @$els = $()
     @$el = $(@template)
     @$el.find('h2').text(name)
@@ -49,23 +48,23 @@
   addScene: (name) ->
     if name == 'all'
       @appendNoneOption()
-      return @keyword_scenes.all ||= new AllScene(name, @)
+      return @keyword_scenes.all ||= new AllScene(@)
     if !@scenes[name]
-      @scenes[name] = new Scene(name, @)
+      @scenes[name] = new Scene(@, name)
       @$el.find('ul').append(@scenes[name].$el)
     return @scenes[name]
   appendNoneOption: ->
-    if !@scenes['none']
-      @scenes['none'] = new NoneScene(name, @)
-      @$el.find('ul').prepend(@scenes['none'].$el)
+    if !@keyword_scenes.none
+      @keyword_scenes.none = new NoneScene(@)
+      @$el.find('ul').prepend(@keyword_scenes.none.$el)
     @scenes['none']
   setDefault: (scene) ->
     @$default = scene.$el.find('a')
   toggleScene: (name) ->
-    @scenes[name].handleClick()
+    (@scenes[name] || @keyword_scenes[name]).handleClick()

 class Scene
-  constructor: (@name, @stage) ->
+  constructor: (@stage, @name) ->
     @$actors = $()
     @$exclusions = $()
     @$el = $(@template)
@@ -99,11 +98,13 @@
     @toggleActors(@$actors.not(@$exclusions), direction)

 class AllScene extends Scene
+  constructor: (@stage) ->
+    super(@stage, 'all')
   @$exclusions: $.noop()

 class NoneScene extends Scene
-  constructor: (@name, @stage) ->
-    super 'none', @stage
+  constructor: (@stage) ->
+    super(@stage, 'none')
   toggleKeywordAll: ->
     @stage.keyword_scenes.all.toggle(false)


DiaScene: addTransfer: new method to keep transfer data between nodes

--- lib/components/dia/diascene.coffee
+++ lib/components/dia/diascene.coffee
@@ -343,6 +343,51 @@

     @realContext.stroke()

+  addTransfer: (connection, transfer = {}) ->
+
+    transfer = $.extend
+      size        : 3
+      reverse     : no
+      stepSize    : 0.005
+      duration    : 1000
+      font        : '12px sans-serif'
+      color       : 'black'
+      textAlign   : 'center'
+      shadowBlur  : 10
+    , transfer
+
+    transfer.step = 0
+
+    if transfer.duration
+      transfer.stepSize = 1 / (transfer.duration / @updateEvery)
+
+    if transfer.reverse
+      transfer.step = 1
+      transfer.stepSize = -transfer.stepSize  if transfer.stepSize > 0
+
+    hasTransfer = no
+
+    if transfer.id
+      transfers = []
+      connection.options.transfers.forEach (_transfer) ->
+        if transfer.id is _transfer.id
+          transfer.step = _transfer.step
+          transfers.push transfer
+          hasTransfer = yes
+        else
+          transfers.push _transfer
+
+      connection.options.transfers = transfers
+
+    unless hasTransfer
+      transfer.id = KD.utils.getUniqueId()
+      connection.options.transfers.push transfer
+
+    @updateScene()
+
+    return transfer.id
+
+
   addFakeConnection: (connection) ->

     @drawConnectionLine connection


list definitions

list definitions

--- src/github-tfs.coffee
+++ src/github-tfs.coffee
@@ -69,6 +69,7 @@
     tfsDomain = ""

   tfsBuildListAPICall = "_apis/build/builds"
+  tfsBuildDefinitionsAPICall = "_apis/build/definitions"

   if process.env.HUBOT_TFS_DEFAULT_COLLECTION?
     tfsDefaultCollection = process.env.HUBOT_TFS_DEFAULT_COLLECTION
@@ -104,6 +105,19 @@
     }
   ]

+  buildDefinitionsTableDefinition = [
+    {
+      "label" : "ID",
+      "field" : "id",
+      "length" : 5
+    },
+    {
+      "label" : "Name",
+      "field" : "name",
+      "length" : 30
+    }
+  ]
+
   asciiTable = new AsciiTable()

   # Check for required config
@@ -120,16 +134,13 @@
       missingAnything |= true
     missingAnything

-  debugRegex = (aRegexArrayItem) ->
-     robot.logger.debug aRegexArrayItem
+  debugRegex = (aRegexArray) ->
+     robot.logger.debug aRegexArrayItem for aRegexArrayItem in aRegexArray

-
-  ##########################################################
-  #                       COMMAND
-  # hubot tfs build list SpidersFromMars
-  # hubot tfs build list SpidersFromMars from MyCollection
-  ##########################################################
-  robot.respond /tfs build list (\S*)(?: from )?(\S*)/, (res) ->
+  ####
+  # Respond and display a table
+  ####
+  doRespond = (res, apiCallStr, tableDefinition) ->
     # Don't go further if the required environment variables are missing
     return if missingEnvironmentForTFSBuildApi(res)

@@ -140,10 +151,12 @@
       tfsCollection = tfsDefaultCollection

     if tfsPort?
-      tfsURL = "#{tfsProtocol}://#{tfsServer}:#{tfsPort}#{tfsURLPrefix}#{tfsCollection}/#{tfsProject}/#{tfsBuildListAPICall}"
+      tfsURL = "#{tfsProtocol}://#{tfsServer}:#{tfsPort}#{tfsURLPrefix}#{tfsCollection}/#{tfsProject}/#{apiCallStr}"
     else
-      tfsURL = "#{tfsProtocol}://#{tfsServer}#{tfsURLPrefix}#{tfsCollection}/#{tfsProject}/#{tfsBuildListAPICall}"
+      tfsURL = "#{tfsProtocol}://#{tfsServer}#{tfsURLPrefix}#{tfsCollection}/#{tfsProject}/#{apiCallStr}"

+    robot.logger.debug tfsURL
+
     tfsApiCall = {
       "url": tfsURL,
       "username": tfsUsername,
@@ -151,6 +164,7 @@
       "workstation": tfsWorkstation,
       "domain": tfsDomain
     }
+

     httpntlm.get tfsApiCall, (apiCallErr, apiCallRes) ->
       if apiCallErr
@@ -161,9 +175,22 @@
         return
       else
         result = JSON.parse apiCallRes.body
-        res.reply "Found #{result.count} builds for #{tfsProject} in #{tfsCollection}"
-        tableResult = asciiTable.buildTable(buildListTableDefinition, result.value)
+        res.reply "Found #{result.count} results for #{tfsProject} in #{tfsCollection}"
+        tableResult = asciiTable.buildTable(tableDefinition, result.value)
         res.reply tableResult
+
+
+
+  ##########################################################
+  #                       COMMAND
+  # hubot tfs build list SpidersFromMars
+  # hubot tfs build list SpidersFromMars from MyCollection
+  ##########################################################
+  robot.respond /tfs build list (\S*)(?: from )?(\S*)/, (res) ->
+    doRespond(res, tfsBuildListAPICall, buildListTableDefinition)
+
+  robot.respond /tfs build definitions (\S*)(?: from )?(\S*)/, (res) ->
+    doRespond(res, tfsBuildDefinitionsAPICall, buildDefinitionsTableDefinition)

   robot.hear /orly/, (res) ->


Have a grand heading.

Have a grand heading.

--- app/clipboard.coffee
+++ app/clipboard.coffee
@@ -23,7 +23,7 @@
       clipboard_clients().append client_xref name

 @open_clipboard = ->
-  ($ ".arena form").remove()
+  ($ ".arena").append h1().text "Ablage"
   ($ ".arena").append containment = make_containment()
   VIEWS_TABLE_SELECTION = $.extend {}, VIEWS_TABLE, VIEWS_TABLE_EDIT, {button_add: do_nothing, button_edit: do_nothing}
   c = do (xreflist) ->


Added `exports.register` to user/register, and exposed it to the REPL

--- server/packages/user/register.coffee
+++ server/packages/user/register.coffee
@@ -11,24 +11,44 @@

 	receiver: (req, fn) ->

-		{models: User: User} = require 'server/jugglingdb'
+		{body} = req
+		{email, password, username} = body

-		user = new User name: req.body.username
+		exports.register(username, email, password).then((user) ->

-		# Encrypt the email.
-		(crypto.encrypt req.body.email).then((encryptedEmail) ->
-
-			user.email = encryptedEmail
-
-			crypto.hasher()
-
-		).then((opts) ->
-
-			user.salt = opts.salt.toString 'hex'
-			user.passwordHash = opts.key.toString 'hex'
-
-			# TODO: email req.body.email with opts.plaintext
-
-			user.save()
+			# TODO: email with password ? opts.plaintext

 		).nodeify fn
+
+exports.$replContext = (context) ->
+
+	schema = require 'server/jugglingdb'
+
+	context.registerUser = (name, email, password) ->
+
+		_register name, email, password, schema
+
+exports.register = (name, email, password) ->
+
+	_register name, email, password, require 'server/jugglingdb'
+
+_register = (name, email, password, schema) ->
+
+	{models: User: User} = schema
+	user = new User name: name
+
+	# Encrypt the email.
+	crypto.encrypt(email).then((encryptedEmail) ->
+
+		user.email = encryptedEmail
+
+		crypto.hasher plaintext: password
+
+	).then((opts) ->
+
+		user.salt = opts.salt.toString 'hex'
+		user.passwordHash = opts.key.toString 'hex'
+
+		user.save()
+
+	)


preventing 1,000,000 tears ;)

preventing 1,000,000 tears ;)

--- workers/social/lib/social/models/group/permissionset.coffee
+++ workers/social/lib/social/models/group/permissionset.coffee
@@ -18,14 +18,14 @@
   @share()

   @set
-    index                   :
+    indexes                 :
       'permissions.module'  : 'sparse'
       'permissions.roles'   : 'sparse'
       'permissions.title'   : 'sparse'
     schema                  :
       permissions           :
         type                : Array
-        default             : []
+        default             : -> []

   {intersection} = require 'underscore'


Mapping API: support for direct construction

Instead of creating a `new Mapping` and then calling the `load` method,
you can now just create a `Mapping` with a specified function or object
mapping keys to content via `new Mapping(data)`.

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -526,8 +526,11 @@
     @styles.push map

 class Mapping extends Input
+  constructor: (data) ->
+    super()
+    @map = {}
+    @load data if data?
   load: (data) ->
-    @map = {}
     if typeof data == 'function'
       @function = data
     else


Output svgtiler version in help

Output svgtiler version in help

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -968,6 +968,7 @@

 help = ->
   console.log """
+svgtiler #{svgtiler.version ? "(web)"}
 Usage: #{process.argv[1]} (...options and filenames...)
 Documentation: https://github.com/edemaine/svgtiler#svg-tiler


Add @android

Add @android

--- scripts/mobile-mention.coffee
+++ scripts/mobile-mention.coffee
@@ -23,6 +23,13 @@

     return str

+  android = [
+    "[@Dupuis, Brian](#/person/53a2ed209cfde35e23298300) [@Zhang, Derrick](#/person/561fcf4e3e0d3317a49d846f) [@Whitehead, Carlton](#/person/53a2ed3e9cfde35e2329840e) http://s2.dmcdn.net/TJqPZ/x240-slL.jpg",
+    "[@Dupuis, Brian](#/person/53a2ed209cfde35e23298300) [@Zhang, Derrick](#/person/561fcf4e3e0d3317a49d846f) [@Whitehead, Carlton](#/person/53a2ed3e9cfde35e2329840e) https://i.ytimg.com/vi/czhDhxFfZsM/maxresdefault.jpg"
+    "[@Dupuis, Brian](#/person/53a2ed209cfde35e23298300) [@Zhang, Derrick](#/person/561fcf4e3e0d3317a49d846f) [@Whitehead, Carlton](#/person/53a2ed3e9cfde35e2329840e) http://www.cat-gifs.com/w3/Artistic-CAT-GIF-Funny-cat-sticking-out-his-cute-pink-tongue-Trompe-l-oeil-effect.gif"
+    "[@Dupuis, Brian](#/person/53a2ed209cfde35e23298300) [@Zhang, Derrick](#/person/561fcf4e3e0d3317a49d846f) [@Whitehead, Carlton](#/person/53a2ed3e9cfde35e2329840e) http://tailandfur.com/wp-content/uploads/2016/03/40-Scary-and-Funny-Cat-Pictures-Feature-Image.jpg"
+  ]
+
   robot.hear /@mobile/i, (msg) ->
     console.log 'msg', msg
     console.log 'msg.message.text', msg.message.text
@@ -30,3 +37,6 @@
     console.log 'msg.message.user', msg.message.user
     msg.send mention(msg.message.user.name, msg.message.text)

+  robot.hear /@android/i, (msg) ->
+    msg.send msg.random android
+


fix a small bug in public paths

fix a small bug in public paths

--- salvus/page/project_store.coffee
+++ salvus/page/project_store.coffee
@@ -672,6 +672,7 @@
         _compute_public_files: (listing) =>
             v = @get_public_paths()
             if v? and v.size > 0
+                head = if @state.current_path then @state.current_path + '/' else ''
                 paths = []
                 map   = {}
                 for x in v.toJS()
@@ -679,7 +680,7 @@
                     paths.push(x.path)
                 window.paths = paths
                 for x in listing
-                    p = misc.containing_public_path(@state.current_path + '/' + x.name, paths)
+                    p = misc.containing_public_path(head + x.name, paths)
                     if p?
                         x.public = map[p]


Fix bug where updating a wish, save doesn't work

--- client/view/wishes.coffee
+++ client/view/wishes.coffee
@@ -14,10 +14,11 @@
   'click .edit':  ->
     Wishes.update(@_id, $set: editing: (not @editing))

-  'click .update': (event, context) ->
+  'click .save': (event, context) ->
     title = context.find("#title-#{@_id}").value
     description = context.find("#description-#{@_id}").value
-    Wishes.update(@_id, $set: {editing: false, title: title, description: description})
+    if title
+      Wishes.update(@_id, $set: {editing: false, title: title, description: description})

   'click .vote-up': ->
     u = Meteor.userId()


Compare the robot name and not the length of the name.

--- src/telegram.coffee
+++ src/telegram.coffee
@@ -93,7 +93,7 @@
             text = text.replace(/^\//g, '')

             # If it is a private chat, automatically prepend the bot name if it does not exist already.
-            if (message.chat.id > 0 && text.substr(0, @robot.name.length) != @robot.name.length)
+            if (message.chat.id > 0 && text.substr(0, @robot.name.length) != @robot.name)
                 text = @robot.name + ' ' + text

             @robot.logger.debug "Received message: " + message.from.username + " said '" + text + "'"