Added some underbars.

Added some underbars.

--- src/lib/packet.coffee
+++ src/lib/packet.coffee
@@ -15,25 +15,27 @@
 class Packet extends stream.Stream
   # Construct a packet that sends events using the given `self` as `this`.
   constructor: (@self) ->
-    @packets = {}
+    @_packets = {}
     @reset()
-    @fields = []
+    @_fields = []
     @transforms = Object.create(transforms)

   # Create a copy that will adopt the packets defined in this object, through
   # prototype inheritence. This is used to efficently create parsers and
   # serializers that can run concurrently, from a pre-configured prototype,
   # following classic GoF prototype creational pattern.
+  #
+  # FIXME Test me.
   clone: ->
-    copy            = new (this.constructor)()
-    copy.packets    = Object.create(packets)
+    copy            = new (@.constructor)()
+    copy._packets   = Object.create(@_packets)
     copy

   # Map a named packet with the given `name` to the given `pattern`.
   packet: (name, pattern, callback) ->
     callback      or= noop
     pattern         = parsePattern(pattern)
-    @packets[name]  = {pattern, callback}
+    @_packets[name] = {pattern, callback}

   # Resets the bytes read, bytes written and the current pattern. Used to
   # recover from exceptional conditions and generally a good idea to call this
@@ -43,7 +45,7 @@
     @bytesWritten = 0
     @pattern = null
     @callback = null
-    @fields = []
+    @_fields = []

   # Excute the pipeline of transforms for the `pattern` on the `value`.
   pipeline: (pattern, value) ->
@@ -201,7 +203,7 @@
     @_skipping    = null
     @terminated   = not pattern.terminator

-    @fields.push [] if pattern.arrayed and pattern.endianness isnt "x"
+    @_fields.push [] if pattern.arrayed and pattern.endianness isnt "x"

   nextValue: ->
     pattern = @pattern[@patternIndex]
@@ -223,14 +225,14 @@
   # pattern, with an optional `callback`. The optional `callback` will override
   # the callback assigned to a named pattern.
   parse: (nameOrPattern, callback) ->
-    packet        = @packets[nameOrPattern] or {}
+    packet        = @_packets[nameOrPattern] or {}
     pattern       = packet.pattern or parsePattern(nameOrPattern)
     callback    or= packet.callback or noop

     @pattern      = pattern
     @callback     = callback
     @patternIndex = 0
-    @fields       = []
+    @_fields      = []

     @nextField()
     @nextValue()
@@ -244,7 +246,7 @@
     @index        = 0
     @repeat       = 1
     @patternIndex = 0
-    @fields = [ length ]
+    @_fields      = [ length ]

     @_skipping = length

@@ -325,9 +327,9 @@
         # If we are filling an array field the current fields is an array,
         # otherwise current field is the value we've just read.
         if @pattern[@patternIndex].arrayed
-          @fields[@fields.length - 1].push(field) if field?
+          @_fields[@_fields.length - 1].push(field) if field?
         else
-          @fields.push(field)
+          @_fields.push(field)

       # If we've not yet hit our terminator, check for the terminator. If we've
@@ -356,23 +358,23 @@
       # The pattern is set to null, our terminal condition, because the callback
       # may specify a subsequent packet to parse.
       else if ++@patternIndex == @pattern.length
-        @fields.push(@pipeline(@pattern[@patternIndex - 1 ], @fields.pop()))
-
-        @fields.push(this)
+        @_fields.push(@pipeline(@pattern[@patternIndex - 1 ], @_fields.pop()))
+
+        @_fields.push(this)
         for p in @user or []
-          @fields.push(p)
+          @_fields.push(p)

         @pattern = null

-        @callback.apply @self, @fields
+        @callback.apply @self, @_fields

       # Otherwise we proceed to the next field in the packet pattern.
       else
         if @pattern[@patternIndex - 1].endianness isnt "x"
           if @pattern[@patternIndex - 1].length
-            @pattern[@patternIndex].repeat = @fields.pop()
+            @pattern[@patternIndex].repeat = @_fields.pop()
           else
-            @fields.push(@pipeline(@pattern[@patternIndex - 1], @fields.pop()))
+            @_fields.push(@pipeline(@pattern[@patternIndex - 1], @_fields.pop()))

         @nextField()
         @nextValue()
@@ -419,7 +421,7 @@

   packet: (name, pattern, callback) ->
     super name, pattern, callback
-    for part in @packets[name].pattern
+    for part in @_packets[name].pattern
       if part.transforms
         part.transforms.reverse()

@@ -494,7 +496,7 @@
       callback = shiftable.pop()

     nameOrPattern = shiftable.shift()
-    packet        = @packets[nameOrPattern] or {}
+    packet        = @_packets[nameOrPattern] or {}
     pattern       = packet.pattern or parsePattern(nameOrPattern)
     callback    or= packet.callback or noop


Check node if its real before doing anything

--- client/Framework/components/tree/treeviewcontroller.coffee
+++ client/Framework/components/tree/treeviewcontroller.coffee
@@ -107,7 +107,7 @@
     @nodes[@getNodeId nodeData] = {}

     if options.putDepthInfo
-      if @nodes[@getNodePId nodeData]
+      if @nodes[@getNodePId nodeData]?.getData?
         nodeData.depth = @nodes[@getNodePId(nodeData)].getData().depth + 1
       else
         nodeData.depth = 0
@@ -160,6 +160,8 @@
   addNode:(nodeData, index)->

     nodeData = @repairIds nodeData
+
+
     return unless nodeData
     @getData().push nodeData
     @addIndexedNode nodeData
@@ -224,11 +226,12 @@
     nodeData = nodeView.getData()
     nodeView.$().attr "draggable","true" if @getOptions().dragdrop
     {id, parentId} = nodeData
+
     @nodes[@getNodeId nodeData] = nodeView
     if @nodes[@getNodePId nodeData]
       @expand @nodes[@getNodePId nodeData] unless @getOptions().addListsCollapsed
       # todo: make decoration with events
-      @nodes[@getNodePId nodeData].decorateSubItemsState()
+      @nodes[@getNodePId nodeData]?.decorateSubItemsState?()
     return unless @listControllers[id]
     @addSubList nodeView, id

@@ -254,7 +257,7 @@

     # if node parent is present
     parentNodeView = @nodes[@getNodePId nodeData]
-    if parentNodeView
+    if parentNodeView?.getData?
       prevNeighbor  = getPreviousNeighbor parentNodeView.getData()
       neighborIndex = @indexedNodes.indexOf prevNeighbor
       @indexedNodes.splice neighborIndex + 1, 0, nodeData
@@ -303,7 +306,7 @@

     o = @getOptions()
     listToBeAdded = @listControllers[id].getView()
-    if nodeView
+    if nodeView?.$?
       nodeView.$().after listToBeAdded.$()
       listToBeAdded.parentIsInDom = yes
       listToBeAdded.propagateEvent KDEventType: 'viewAppended'
@@ -432,9 +435,10 @@

   expand:(nodeView)->

-    nodeData = nodeView.getData()
-    nodeView.expand()
-    @listControllers[@getNodeId nodeData]?.getView().expand()
+    if nodeView?.getData?
+      nodeData = nodeView.getData()
+      nodeView.expand()
+      @listControllers[@getNodeId nodeData]?.getView().expand()

   collapse:(nodeView)->


Added vote up/down icons when not logged in above/below vote amount

--- core/app/backbone/views/evidence/evidence_vote_view.coffee
+++ core/app/backbone/views/evidence/evidence_vote_view.coffee
@@ -24,24 +24,32 @@
       'spec-evidence-vote-down'
     ].join(' ')

-    R.div className: 'comment-votes',
-      if Factlink.Global.signed_in
-        R.a
-          className: up_classes
-          href: "javascript:"
-          onClick: @_on_up_vote
-          R.i className: "icon-up-open"
-      else
-        R.span(className: up_classes)
+    if Factlink.Global.signed_in
+      R.div className: 'comment-votes',
+    else
+      R.div className: 'comment-votes disabled',

-      R.span className:"comment-vote-amount spec-evidence-relevance",
-        format_as_short_number(@model().relevance())
+        if Factlink.Global.signed_in
+          R.a
+            className: up_classes
+            href: "javascript:"
+            onClick: @_on_up_vote
+            R.i className: "icon-up-open"
+        else
+          R.span
+            className: up_classes
+            R.i className: "icon-up-open"

-      if Factlink.Global.signed_in
-        R.a
-          className: down_classes
-          href: "javascript:"
-          onClick: @_on_down_vote
-          R.i className: "icon-down-open"
-      else
-        R.span(className: down_classes)
+        R.span className:"comment-vote-amount spec-evidence-relevance",
+          format_as_short_number(@model().relevance())
+
+        if Factlink.Global.signed_in
+          R.a
+            className: down_classes
+            href: "javascript:"
+            onClick: @_on_down_vote
+            R.i className: "icon-down-open"
+        else
+          R.span
+            className: down_classes
+            R.i className: "icon-down-open"


Fix newline

Fix newline

--- lib/Bumped.animation.coffee
+++ lib/Bumped.animation.coffee
@@ -17,13 +17,11 @@
   start: (cb) ->
     @start = new Date()
     @running = true
-
     shortcut = TYPE_SHORTCUT[@type]
-    process.stdout.write '\n' if @type is 'postrelease'
+    process.stdout.write '\n' if @isPostRelease

     @logger.keyword = "#{chalk.magenta(shortcut)} #{@logger.keyword}"
     @logger.success "Starting '#{chalk.cyan(@text)}'..."
-
     cb()

   stop: (err, cb) ->
@@ -32,7 +30,7 @@

     end = ms new Date() - @start
     @logger.success "Finished '#{chalk.cyan(@text)}' after #{chalk.magenta(end)}."
-    process.stdout.write '\n' unless @isLast and @isPostRelease
+    process.stdout.write '\n' if @isPreRelease
     @logger.keyword = DEFAULT.logger.keyword

     cb err


GroupStackSettings: pass credentials(publicKeys) as array

--- client/admin/lib/views/groupstacksettings.coffee
+++ client/admin/lib/views/groupstacksettings.coffee
@@ -120,7 +120,7 @@
   setStack: (stackTemplate) ->

     terraformContext = @editorPane.getValue()
-    publicKeys = aws : @credentialBox.getValue()
+    publicKeys = [@credentialBox.getValue()]

     console.log {terraformContext, publicKeys}


Change default py.test command to python -m pytest

--- lib/atom-python-test.coffee
+++ lib/atom-python-test.coffee
@@ -9,7 +9,7 @@
   config:
     pytestPath:
       type: 'string'
-      default: 'py.test'
+      default: 'python -m pytest'
       title: 'Path to py.test'
       description: '''
       Optional. Set it if default values are not working for you or you want to use specific


threshold: 1.0 doesn't work if the viewport is smaller than the anchor element.

--- resources/assets/lib/back-to-top.coffee
+++ resources/assets/lib/back-to-top.coffee
@@ -61,11 +61,6 @@
       # anchor to body if none specified; assumes body's top is 0.
       target = @props.anchor?.current ? document.body

-      options =
-        root: null
-        rootMargin: '0px'
-        threshold: 1.0
-
       callback = (entries) =>
         for entry in entries
           if entry.target == target && entry.boundingClientRect.top == 0
@@ -73,7 +68,7 @@
             document.addEventListener 'scroll', @onScroll
             break

-      @observer = new IntersectionObserver(callback, options)
+      @observer = new IntersectionObserver(callback)
       @observer.observe(target)
     else
       Timeout.set 0, () =>


Prevent error on add stream

Prevent error on add stream

--- packages/rocketchat-webrtc/WebRTCClass.coffee
+++ packages/rocketchat-webrtc/WebRTCClass.coffee
@@ -618,7 +618,7 @@
 		if peerConnection.iceConnectionState isnt 'new'
 			return

-		peerConnection.addStream @localStream if @localStream
+		try peerConnection.addStream @localStream if @localStream

 		onAnswer = (answer) =>
 			onLocalDescription = =>


[Backport] fix membership pagination

[Backport] fix membership pagination

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -82,6 +82,7 @@
         return @rs.memberships.list(@scope.projectId, httpFilters).then (data) =>
             @scope.memberships = _.filter(data.models, (membership) ->
                                     membership.user == null or membership.is_user_active)
+
             @scope.page = data.current
             @scope.count = data.count
             @scope.paginatedBy = data.paginatedBy
@@ -110,7 +111,7 @@
 ## Member Avatar Directive
 #############################################################################

-MembershipsDirective = ($template) ->
+MembershipsDirective = ($template, $compile) ->
     template = $template.get("admin/admin-membership-paginator.html", true)

     linkPagination = ($scope, $el, $attrs, $ctrl) ->
@@ -158,7 +159,11 @@
                 else
                     pages.push({classes: "page", num: i, type: "page"})

-            $pagEl.html(template(options))
+            html = template(options)
+            html = $compile(html)($scope)
+
+            $pagEl.html(html)
+            $pagEl.show()

         $scope.$watch "memberships", (value) ->
             # Do nothing if value is not logical true
@@ -198,7 +203,7 @@

     return {link:link}

-module.directive("tgMemberships", ["$tgTemplate", MembershipsDirective])
+module.directive("tgMemberships", ["$tgTemplate", "$compile", MembershipsDirective])

 #############################################################################
@@ -400,6 +405,10 @@
             $confirm.askOnDelete(title, message).then (finish) ->
                 onSuccess = ->
                     finish()
+
+                    if $scope.page > 1 && ($scope.count - 1) <= $scope.paginatedBy
+                        $ctrl.selectFilter("page", $scope.page - 1)
+
                     $ctrl.loadMembers()

                     text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_DELETE")


Agent pie: edit pointer transition

Agent pie: edit pointer transition


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -84,7 +84,7 @@
         .attr('fill', "#fff")
         .attr("text-anchor", "middle")
         .attr("alignment-baseline", "mathematical")
-        .attr('font-size', radiusi*1.5 + 'px')
+        .attr('font-size', radiusi*1.3 + 'px')
         .attr('font-weight', 'bold')
         .text(sum)
         .attr("filter","url(#dropshadow)")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -235,6 +235,7 @@
         #    .duration(300)
         #    .attrTween("d", that.removePieTween)
         #    .remove();
+        path.exit()

         labels = labelGroup.selectAll("text")
             .data(data.sort((p1,p2) -> return p1.startAngle - p2.startAngle))


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -272,8 +272,9 @@
         if labelGroup.empty()
             labelGroup = svg.append("g").attr("class", "labels")

-        #path = pathGroup.selectAll("path.pie")
-        #    .data(piedata)
+        path = pathGroup.selectAll("path.pie")
+            .data(piedata)
+        console.log("path",path)

         #path.enter().append("path")
         #    .attr("class", "pie")


vm: added "usage" and also "calculateUsage"

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -20,7 +20,7 @@
       'list all vms'    : ['member','moderator']
       'list default vm' : ['member','moderator']
     sharedMethods       :
-      static            : ['fetchVmsByContext']#,'create']
+      static            : ['fetchVmsByContext','calculateUsage']#,'create']
       instance          : []
     schema              :
       ip                :
@@ -32,6 +32,16 @@
       name              : String
       users             : Array
       groups            : Array
+      usage             :
+        cpu             :
+          type          : Number
+          default       : 1
+        ram             :
+          type          : Number
+          default       : 0.25
+        disk            :
+          type          : Number
+          default       : 0.5
       isEnabled         :
         type            : Boolean
         default         : yes
@@ -69,6 +79,29 @@
   #     target.addLimit limit, 'vm', (err)->
   #       callback err ? null, unless err then limit

+  @getUsageTemplate = -> { cpu: 0, ram: 0, disk: 0 }
+
+  @calculateUsage = (account, groupSlug, callback)->
+    nickname =
+      if 'string' is typeof account then account
+      else account.profile.nickname
+
+    @all { name: ///$#{groupSlug}~#{nickname}~/// }, (err, vms) =>
+      return callback err  if err?
+      callback null, vms
+        .map((vm) -> vm.usage)
+        .reduce (acc, usage) ->
+          for own field, val of usage
+            acc[field] += val
+            return acc
+        , @getUsageTemplate()
+
+
+  @calculateUsage$ = permit 'list all vms',
+    success: (client, groupSlug, callback)->
+      {delegate} = client.connection
+      @calculateUsage delegate, groupSlug, callback
+
   @fetchVmsByContext = permit 'list all vms',
     success: (client, options, callback) ->
       [callback, options] = [options, callback]  unless callback


 Merge branch 'master.groups.sharedVMCreation' into 'master.groups'

Groups.sharedVM: Fixed bug in sharedVM creation.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -371,7 +371,7 @@

           JPaymentPack.one tags: "vm", (err, pack) =>
             return callback err  if err or not pack
-            subscription.debit {pack}, 1, (err) =>
+            subscription.debit {pack}, (err) =>
               return callback err  if err
               @createVm {
                 type      : "group"


Create relationship event domain creation fails.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -79,17 +79,16 @@
         domainObj.regYears      = 0
         domainObj.save (err)->
           console.log err  if err?
-          unless err
-            Relationship.one
-              targetName: "JDomain",
-              targetId: domainObj._id,
-              sourceName: "JAccount",
-              sourceId: account._id,
-              as: "owner"
-            , (err, rel)->
-              if err or not rel
-                account.addDomain domainObj, (err)->
-                  console.log err  if err?
+          Relationship.one
+            targetName: "JDomain",
+            targetId: domainObj._id,
+            sourceName: "JAccount",
+            sourceId: account._id,
+            as: "owner"
+          , (err, rel)->
+            if err or not rel
+              account.addDomain domainObj, (err)->
+                console.log err  if err?

   @fixUserDomains = permit 'change bundle',
     success: (client, callback)->


regexp captures domain-only URLs correctly now

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -165,7 +165,7 @@

   expandUrls: (text) ->
     return null unless text
-    text.replace /([a-zA-Z]+\:\/\/)?(\w+:\w+@)?[a-zA-Z\d\.-]+\.([a-zA-Z]{2,4}(:\d+)?)[\/\?][\S\/]*\b\/?/g, (url) ->
+    text.replace /([a-zA-Z]+\:\/\/)?(\w+:\w+@)?[a-zA-Z\d\.-]+\.([a-zA-Z]{2,4}(:\d+)?)([\/\?][\S\/]*)*\b\/?/g, (url) ->
       originalUrl = url

       # remove protocol and trailing path


 Merge branch 'master.groupsappcontrollerfix' into 'master'

Master.Groupsappcontrollerfix

--- client/Groups/AppController.coffee
+++ client/Groups/AppController.coffee
@@ -182,11 +182,18 @@
     KD.whoami().fetchGroupsWithPendingInvitations groupIds:ids, (err, groups)=>
       @markPendingGroupInvitations controller, (group.getId() for group in groups)

+  forEachItemByIndex:(controller, ids, callback)->
+    [callback, ids] = [ids, callback]  unless callback
+    ids = [ids]  unless Array.isArray ids
+    ids.forEach (id)=>
+      item = controller.itemsIndexed[id]
+      callback item  if item?
+
   markPendingRequestGroups:(controller, ids)->
-    controller.forEachItemByIndex ids, (view)-> view.markPendingRequest()
+    @forEachItemByIndex controller, ids, (view)-> view.markPendingRequest()

   markPendingGroupInvitations:(controller, ids)->
-    controller.forEachItemByIndex ids, (view)-> view.markPendingInvitation()
+    @forEachItemByIndex controller, ids, (view)-> view.markPendingInvitation()

   monitorGroupItemOpenLink:(item)->
     item.on 'PrivateGroupIsOpened', @bound 'openPrivateGroup'


Documented StringUtil class

Documented StringUtil class

--- www/src/theoricus/utils/string_util.coffee
+++ www/src/theoricus/utils/string_util.coffee
@@ -1,15 +1,38 @@
+###*
+  utils module
+  @module utils
+###
+
+###*
+  StringUtil class.
+  @class StringUtil
+###
 module.exports = class StringUtil

-  ###
-  @param [String] str
+  ###*
+
+  Capitalize first letter of the given string
+
+  @method ucfirst
+  @static
+  @param str {String}
+  @example
+    StringUtil.ucfirst "theoricus" #returns 'Theoricus'
   ###
   @ucfirst=( str )->
     a = str.substr( 0, 1 ).toUpperCase()
     b = str.substr( 1 )
     return a + b

-  ###
-  @param [String] str
+  ###*
+
+  Convert String to CamelCase pattern.
+
+  @method camelize
+  @static
+  @param str {String}
+  @example
+    StringUtil.camelize "giddy_up" #returns 'GiddyUp'
   ###
   @camelize=( str )->
     parts = [].concat( str.split "_" )
@@ -18,8 +41,15 @@
     # some weirdness happening if we don't return the buffer
     return buffer

-  ###
-  @param [String] str
+  ###*
+
+  Split CamelCase words using underscore.
+
+  @method underscore
+  @static
+  @param str {String}
+  @example
+    StringUtil.underscore "GiddyUp" #returns '_giddy_up'
   ###
   @underscore=( str )->
     str = str.replace( /([A-Z])/g, "_$1" ).toLowerCase()


proper counting also stop listening to destroyed editors

--- lib/indent-detective.coffee
+++ lib/indent-detective.coffee
@@ -64,7 +64,9 @@
       if @isblank l then continue
       if @iscomment [row, 0], ed then continue
       next = @indent l
-      if next == last then continue
+      if next == last
+        @count votes, next
+        continue
       if next.startsWith last
         @count votes, @after next, last
       else if last.startsWith next
@@ -91,7 +93,7 @@
       ed.setSoftTabs false

   run: (ed) ->
-    return if @manual.has(ed)
+    return if @manual.has(ed) or ed.isDestroyed()
     @setSettings ed, @getSettings ed
     status.updateText()
     setTimeout (=> @run ed), 3000


home/homeappview: close modal if url doesnt have /Home path

--- client/home/lib/homeappview.coffee
+++ client/home/lib/homeappview.coffee
@@ -16,6 +16,12 @@
     options.overlay         ?= yes

     super options, data
+
+    { router, mainView } = kd.singletons
+
+    router.on 'RouteInfoHandled', (routeInfo) =>
+      if routeInfo.path.indexOf('/Home') is -1
+        @destroy()

     @addSubView @nav = new kd.TabHandleContainer { cssClass: 'HomeAppView-Nav' }


terminal: do not restore range if there is none

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -238,6 +238,7 @@

   restoreRange: ->
     range = @utils.getSelectionRange()
+    return  if range.startOffset is range.endOffset and range.startContainer is range.endContainer
     @utils.defer =>
       @utils.addRange range


Fix scroll handling on FF

Fix scroll handling on FF

--- assets/javascripts/app.coffee
+++ assets/javascripts/app.coffee
@@ -7,12 +7,14 @@
 staticNav = d3.select("#nav")
 navList = d3.selectAll(".nav-list")
 sticky = false
+doc = document.documentElement

 toggleSticky = ->
-  if not sticky and win.scrollTop > hero.offsetHeight
+  top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0)
+  if not sticky and top > hero.offsetHeight
     nav.classed "hidden", false
     sticky = true
-  else if sticky and win.scrollTop <= hero.offsetHeight
+  else if sticky and top <= hero.offsetHeight
     nav.classed "hidden", true
     sticky = false

@@ -43,7 +45,7 @@

 getSectionId = ->
   winHeight = win.offsetHeight
-  midScroll = win.scrollTop + winHeight * 0.5
+  midScroll = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0) + winHeight * 0.5
   offset = null
   section = "cyberpunk"
   for el in sections[0]
@@ -99,8 +101,8 @@
   scrollTo(navPos)

 scrollTo = (pos) ->
-  if win.scrollTop < pos
-    document.body.scrollTop += 20
+  if (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0) < pos
+    document.documentElement.scrollTop += 20
     setTimeout ->
       scrollTo(pos)
     , 5


only add notice if role is present

only add notice if role is present

--- shared/src/model/notifications.coffee
+++ shared/src/model/notifications.coffee
@@ -75,11 +75,10 @@
   # The notification logic may display a notice
   # based on the relationship or if student identifier is missing
   setCourseRole: (course, role) ->
-    return if role.type is 'teacher'
-    unless isEmpty(role)
-      studentId = find(course.students, role_id: role.id)?.student_identifier
-      if isEmpty(studentId) and moment().diff(role.joined_at, 'days') > 7
-        @display({type: @POLLING_TYPES.MISSING_STUDENT_ID, course, role})
+    return if isEmpty(role) or role.type is 'teacher'
+    studentId = find(course.students, role_id: role.id)?.student_identifier
+    if isEmpty(studentId) and moment().diff(role.joined_at, 'days') > 7
+      @display({type: @POLLING_TYPES.MISSING_STUDENT_ID, course, role})
     if moment(course.ends_at).isBefore(moment(), 'day')
       @display({type: @POLLING_TYPES.COURSE_HAS_ENDED, course, role})


TitleView: h3 -> span

TitleView: h3 -> span

--- coffee/src/views/title_view.coffee
+++ coffee/src/views/title_view.coffee
@@ -21,7 +21,7 @@

   defaults: => _.extend super,
     # tagName for internal title
-    titleTag: 'h3'
+    titleTag: 'span'

   initialize: =>


Only require active provider

Only require active provider

--- lib/provider-manager.coffee
+++ lib/provider-manager.coffee
@@ -2,8 +2,10 @@
 ScopedPropertyStore = require('scoped-property-store')
 _ = require('underscore-plus')
 Uuid = require('node-uuid')
-SymbolProvider = require('./symbol-provider')
-FuzzyProvider = require('./fuzzy-provider')
+
+# Deferred requires
+SymbolProvider = null
+FuzzyProvider =  null

 module.exports =
 class ProviderManager
@@ -60,8 +62,10 @@
     if enabled
       return if @fuzzyProvider? or @fuzzyRegistration?
       if atom.config.get('autocomplete-plus.defaultProvider') is 'Symbol'
+        SymbolProvider ?= require('./symbol-provider')
         @fuzzyProvider = new SymbolProvider()
       else
+        FuzzyProvider ?= require('./fuzzy-provider')
         @fuzzyProvider = new FuzzyProvider()
       @fuzzyRegistration = @registerProvider(@fuzzyProvider)
     else


Improved _sortBy().

Improved _sortBy().

--- app/assets/javascripts/app/models/_application_model.js.coffee
+++ app/assets/javascripts/app/models/_application_model.js.coffee
@@ -248,18 +248,32 @@
     if params.order
       all_complied = @_order( all_complied, params.order )

-    return all_complied
+    all_complied

   @_sortBy: ( collection, attribute ) ->
     _.sortBy( collection, (item) ->
-      return '' if item[ attribute ] is undefined || item[ attribute ] is null
-      return item[ attribute ].toLowerCase()
+
+      # set displayName as default sort attribute
+      if !attribute
+        attribute = 'displayName'
+
+      # check if displayName exists
+      if attribute is 'displayName' && item.displayName
+        return item.displayName().toLowerCase()
+      else
+        attribute = 'name'
+
+      return '' if item[ attribute ] is undefined
+      return '' if item[ attribute ] is null
+
+      # return value
+      item[ attribute ].toLowerCase()
     )

   @_order: ( collection, attribute ) ->
     if attribute is 'DESC'
       return collection.reverse()
-    return collection
+    collection

   @_filter: ( collection, filter ) ->
     for key, value of filter
@@ -267,7 +281,7 @@
         if item[ key ] is value
           return item
       )
-    return collection
+    collection

   @_filterExtended: ( collection, filters ) ->
     collection = _.filter( collection, (item) ->
@@ -292,6 +306,6 @@

       return
     )
-    return collection
+    collection


read exif

read exif

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -15,13 +15,19 @@
     # in -> file
     # out <- blob
     df = $.Deferred()
+    exif = null
+    exifTags = null

     if not URL
       df.reject('support')

     else
       ns.readJpegChunks(file).progress (marker, view) ->
-        console.log(marker, view.byteLength)
+        if marker == 0xe1
+          exifTags = ns.parseExifTags(view)
+          if exifTags?
+            exif = view.buffer
+          console.log(exifTags)

       start = new Date()
       img = new Image()
@@ -113,3 +119,71 @@
         readNext()

     df.promise()
+
+
+  ns.parseExifTags = (view) ->
+    if view.byteLength < 6 + 2
+      return
+    if view.getUint32(0) != 0x45786966 or view.getUint16(4) != 0
+      return
+
+    tiffOffset = 6
+    if view.getUint16(tiffOffset) == 0x4949
+        littleEnd = true
+    else if view.getUint16(tiffOffset) == 0x4d4d
+        littleEnd = false
+    else
+        return
+
+    if view.getUint16(tiffOffset + 2, littleEnd) != 0x002a
+        return
+
+    entryOffset = view.getUint32(tiffOffset + 4, littleEnd)
+    if entryOffset < 8
+        return
+    entryOffset += tiffOffset + 2
+
+    entries = view.getUint16(entryOffset - 2, littleEnd)
+    tags = {}
+
+    for i in [0...entries]
+      tag = view.getUint16(entryOffset, littleEnd)
+      value = readTagValue(view, entryOffset + 2, littleEnd)
+      if value?
+        tags[tag] = value
+      entryOffset += 12
+    tags
+
+
+  readTagValue = (view, entryOffset, littleEnd) ->
+    type = view.getUint16(entryOffset, littleEnd)
+    numValues = view.getUint32(entryOffset += 2, littleEnd)
+    valueOffset = view.getUint32(entryOffset += 4, littleEnd) + 6
+
+
+    if type == 2  # ascii, 8-bit byte
+      offset = if numValues > 4 then valueOffset else entryOffset
+      buffer = view.buffer.slice(offset, offset + numValues - 1)
+      return String.fromCharCode.apply(null, new Uint8Array(buffer))
+
+    if numValues == 1
+      switch type
+        # byte, 8-bit unsigned int
+        # undefined, 8-bit byte, value depending on field
+        when 1, 7
+          return view.getUint8(entryOffset)
+
+        when 3  # short, 16 bit int
+          return view.getUint16(entryOffset, littleEnd)
+
+        when 4  # long, 32 bit int
+          return view.getUint32(entryOffset, littleEnd)
+
+        when 5  # rational = two long values, first is numerator, second is denominator
+          return view.getUint32(valueOffset, littleEnd) / view.getUint32(valueOffset + 4, littleEnd)
+
+        when 9  # slong, 32 bit signed int
+          return view.getInt32(entryOffset, littleEnd)
+
+        when 10  # signed rational, two slongs, first is numerator, second is denominator
+          return view.getInt32(valueOffset, littleEnd) / view.getInt32(valueOffset + 4, littleEnd)


html-escape screenreader text in Edit Sections dialog

test plan:
 0. create a course section that contains a script, e.g.
    <script>alert('hello')</script>
 1. on the course People page, click the gear menu next to a user
    and select "Edit Sections"
 2. find the section name in the list
 3. ensure the script doesn't run when you click the section

fixes CNVS-17450

Change-Id: I177dbcc06c2105a2b3af2b293060df71533aaf5a
Reviewed-on: https://gerrit.instructure.com/45836
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Matt Fairbourn <5f353e1c97bb9cc046b9db20beca0b9392f6f1b2@instructure.com>

--- app/coffeescripts/views/courses/roster/EditSectionsView.coffee
+++ app/coffeescripts/views/courses/roster/EditSectionsView.coffee
@@ -63,7 +63,7 @@
       $link.attr('href', '#')
       $link.attr('title', I18n.t("remove_user_from_course_section", "Remove user from %{course_section}", course_section: $token.find('div').attr('title')))
       $screenreader_span = $('').append(I18n.t("remove_user_from_course_section",
-        "Remove user from %{course_section}", course_section: $token.find('div').attr('title')))
+        "Remove user from %{course_section}", course_section: h($token.find('div').attr('title'))))
       $link.append($screenreader_span)

     update: (e) =>


Adds new label; sorts

Adds new label; sorts

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -74,7 +74,10 @@
 module.exports.setDimension = (index, value) ->
   @ga? 'set', index, value

+# Event labels in alphabetical (key) order
 module.exports.en =
   CREATE_CHANNEL: 'New Channel created'
+  CREATE_CONNECTION: 'Connection created'
+  DESTROY_CONNECTION: 'Connection removed'
+  PREMIUM_CHARGE_INITIATED: 'User clicked to submit charge for premium'
   PREMIUM_PAID: 'User paid for pro account'
-  PREMIUM_CHARGE_INITIATED: 'User clicked to submit charge for premium'


[ ! ] Route bugfixes

[ ! ] Route bugfixes

--- lib/arcabouco.coffee
+++ lib/arcabouco.coffee
@@ -161,9 +161,13 @@
     buildPattern = pattern.replace /\{(.*?)\}/g,
       ( match, sub1 ) ->
         params.push sub1
-        return '([^\/]+?)'
+        return '([^\/]+)'
+    replacer = '([^\/]+)'
+    n = buildPattern.lastIndexOf( replacer )
+    if n >= 0 && n + replacer.length >= buildPattern.length
+      buildPattern = buildPattern.substring(0, n) + "([^$]+)";
     constructedRoute =
-      regex : new RegExp '^' + buildPattern + '(\\/?\$|\\/?\\?.*$)'
+      regex : new RegExp '^' + buildPattern
       params: params
       index : pattern


Use mousedown instead of click

Use mousedown instead of click

--- source/scripts/views/list/item.coffee
+++ source/scripts/views/list/item.coffee
@@ -9,7 +9,7 @@
     '.count': 'count'

   events:
-    'click': 'click'
+    'mousedown': 'click'

   constructor: ->
     Base.touchify(@events)


Clarified specs for subsequent fetches of brainstem collection

--- spec/brainstem-collection-spec.coffee
+++ spec/brainstem-collection-spec.coffee
@@ -329,19 +329,21 @@
           collection.fetch()
           server.respond()

-        it 'subsequent fetches return data from storage manager cache', ->
+        it 'returns data from storage manager cache', ->
           collection.fetch()

           expect(collection.pluck 'id').toEqual(_.pluck posts1, 'id')
           expect(collection.pluck 'id').not.toEqual(_.pluck posts2, 'id')

-        it 'subsequent fetch with different options returns different data', ->
-          respondWith(server, '/api/posts?per_page=5&page=2', resultsFrom: 'posts', data: { posts: posts2 })
-          collection.fetch({page: 2})
-          server.respond()
-
-          expect(collection.pluck 'id').not.toEqual(_.pluck posts1, 'id')
-          expect(collection.pluck 'id').toEqual(_.pluck posts2, 'id')
+        context 'different options are provided', ->
+          beforeEach ->
+            respondWith(server, '/api/posts?per_page=5&page=2', resultsFrom: 'posts', data: { posts: posts2 })
+            collection.fetch({page: 2})
+            server.respond()
+
+          it 'updates collection with new data', ->
+            expect(collection.pluck 'id').not.toEqual(_.pluck posts1, 'id')
+            expect(collection.pluck 'id').toEqual(_.pluck posts2, 'id')

   describe '#refresh', ->
     beforeEach ->


Add more specs

Add more specs

--- spec/masked_input_component_spec.cjsx
+++ spec/masked_input_component_spec.cjsx
@@ -246,6 +246,15 @@

             it 'moves the cursor to the correct position', ->
               expect(getInput()._getSelection()).to.eql begin: 3, end: 3
+
+        context 'when the next character is a mask character', ->
+          beforeEach ->
+            simulateKeyPress '3'
+            getInput()._setSelection 2
+            simulateKeyDown 'Backspace'
+
+          it 'correctly shifts the non-mask characters', ->
+            expect(getInputValue()).to.equal '13/__/____'

         context 'when the cursor is at the beginning', ->
           beforeEach ->


Moved base injection after scroll injection so editor offsets are maintained

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -192,6 +192,36 @@

     editorText = @editor.getText()

+    # Scroll into view
+
+    if atom.config.get("atom-html-preview.scrollToCursor")
+      firstSelection = this.editor.getSelections()[0]
+      { row, column } = firstSelection.getBufferRange().start
+
+      try
+        offset = @_getOffset(editorText, row, column)
+
+        tagRE = /<((\/[\$\w\-])|br|input|link)\/?>/.source
+        lastTagRE= ///#{tagRE}(?![\s\S]*#{tagRE})///i
+        findTagBefore = (beforeIndex) ->
+          #sample = editorText.slice(startIndex, startIndex + 300)
+          matchedClosingTag = editorText.slice(0, beforeIndex).match(lastTagRE)
+          if matchedClosingTag
+            return matchedClosingTag.index + matchedClosingTag[0].length
+          else
+            return -1
+
+        tagIndex = findTagBefore(offset)
+        if tagIndex > -1
+          editorText = """
+          #{editorText.slice(0, tagIndex)}
+          #{scrollInjectScript}
+          #{editorText.slice(tagIndex)}
+          """
+
+      catch error
+        return -1
+
     # Add base tag; allow relative links to work despite being loaded
     # as the src of an webview

@@ -211,36 +241,6 @@
     else
       out += baseText

-    # Scroll into view
-
-    if atom.config.get("atom-html-preview.scrollToCursor")
-      firstSelection = this.editor.getSelections()[0]
-      { row, column } = firstSelection.getBufferRange().start
-
-      try
-        offset = @_getOffset(editorText, row, column)
-
-        tagRE = /<((\/[\$\w\-])|br|input|link)\/?>/.source
-        lastTagRE= ///#{tagRE}(?![\s\S]*#{tagRE})///i
-        findTagBefore = (beforeIndex) ->
-          #sample = editorText.slice(startIndex, startIndex + 300)
-          matchedClosingTag = editorText.slice(0, beforeIndex).match(lastTagRE)
-          if matchedClosingTag
-            return matchedClosingTag.index + matchedClosingTag[0].length
-          else
-            return -1
-
-        tagIndex = findTagBefore(offset)
-        if tagIndex > -1
-          editorText = """
-          #{editorText.slice(0, tagIndex)}
-          #{scrollInjectScript}
-          #{editorText.slice(tagIndex)}
-          """
-
-      catch error
-        return -1
-
     out += editorText

     @tmpPath = outPath


show/hide all buttons in card on (un)install

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -113,8 +113,12 @@
     @subscribeToPackageEvent 'package-installed package-install-failed theme-installed theme-install-failed', (pack, error) =>
       @installButton.prop('disabled', false)
       unless error?
+        @updateEnablement()
+
         @installButton.hide()
         @uninstallButton.show()
+        @settingsButton.show()
+        @enablementButton.show()

     @subscribeToPackageEvent 'package-installing', (pack) =>
       @installButton.prop('disabled', true)
@@ -129,6 +133,9 @@
       unless error?
         @installButton.show()
         @uninstallButton.hide()
+        @settingsButton.hide()
+        @enablementButton.hide()
+

     if @isInstalled() or @isDisabled()
       @installButton.hide()


Handle putting empty registers

Handle putting empty registers

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -274,7 +274,8 @@
   #
   # Returns nothing.
   execute: (count=1) ->
-    {text, type} = @vimState.getRegister(@register)
+    {text, type} = @vimState.getRegister(@register) || {}
+    return unless text

     _.times count, =>
       if type == 'linewise' and @location == 'after'


cleanpu

cleanpu

--- src/art/engine/core/element_base.coffee
+++ src/art/engine/core/element_base.coffee
@@ -18,6 +18,9 @@
   2. Element instance registration
   3. name/key property
   4. Inspectors
+
+TODO:
+  5. parent and children relationships
 ###

 module.exports = class ElementBase extends EventedEpochedObject
@@ -27,10 +30,30 @@
     elementFactory.register @ if @registerWithElementFactory()
     super

+  ############################
+  # name/key property
+  ############################
+
+  @concreteProperty
+    name:
+      default: null
+      validate:   (v) -> v == null || isFunction v.toString
+      preprocess: (v) -> if v == null then v else v.toString()
+
+  @virtualProperty
+    key:
+      getter: (pending) -> @getState(pending)._name
+      setter: (v) -> @setName v
+
+
   ##########################
   # Element Registry
   ##########################
   @_elementInstanceRegistry: _elementInstanceRegistry = {}
+
+  @getter
+    isRegistered: -> !!_elementInstanceRegistry[@getInstanceId()]
+
   @getElementByInstanceId: (instanceId) -> _elementInstanceRegistry[instanceId]

   _register: ->
@@ -50,9 +73,6 @@
     @queueEvent "unregistered"
     null

-  @getter
-    isRegistered: -> !!_elementInstanceRegistry[@getInstanceId()]
-
   _updateRegistryFromPendingState: ->
     if pendingParent = @getPendingParent()
       @_register() if pendingParent.getIsRegistered()
@@ -60,19 +80,8 @@
       @_unregister()

   ############################
-  # NAME Property and Inspectors
+  # Inspectors
   ############################
-
-  @concreteProperty
-    name:
-      default: null
-      validate: (v) -> v == null || isFunction v.toString
-      preprocess: (v) -> if v == null then v else v.toString()
-
-  @virtualProperty
-    key:
-      getter: (pending) -> @getState(pending)._name
-      setter: (v) -> @setName v

   @getter
     instanceId: -> @remoteId || @getUniqueId()
@@ -82,8 +91,10 @@

     inspectedName: ->
       "#{@getShortClassPathName()}:#{@instanceId}#{if name = @getPendingName() then ":" + name else ""}"
+
     inspectedNameWithoutIds: ->
       "#{@getShortClassPathName()}#{if name = @getPendingName() then ":" + name else ""}"
+
     inspectedString: -> @inspectedName

   inspectedPropsNotToInclude = ["children", "name", "on"]


Fix error when trying to create gutter items on editor windows that have no gutter

--- lib/php-debug.coffee
+++ lib/php-debug.coffee
@@ -293,7 +293,8 @@
   createGutter: (editor) ->
     if (!editor)
       editor = atom.workspace.getActivePaneItem()
-    if (!editor)
+
+    if (!editor || !editor.gutterWithName)
       return

     gutterEnabled = atom.config.get('php-debug.GutterBreakpointToggle')


Removed metadata fallback when encoding lara provider params

--- src/code/providers/lara-provider.coffee
+++ src/code/providers/lara-provider.coffee
@@ -33,8 +33,8 @@
       launchFromLara: getQueryParam "launchFromLara"
     }
     @removableQueryParams = ['launchFromLara', 'runAsGuest']
-    if @urlParams.launchFromLara
-      @laraParams = @decodeParams @urlParams.launchFromLara
+
+    @laraParams = if @urlParams.launchFromLara then @decodeParams(@urlParams.launchFromLara) else null

     @docStoreUrl = new DocumentStoreUrl @urlParams.documentServer

@@ -279,7 +279,6 @@
     callback "Cannot open the specified document"

   getOpenSavedParams: (metadata) ->
-    # if loaded from lara then reuse the params so we don't leak the keys
-    @encodeParams @laraParams or metadata.providerData
+    @encodeParams @laraParams

 module.exports = LaraProvider


Update default features in settings

Update default features in settings

--- config/settings.development.coffee
+++ config/settings.development.coffee
@@ -130,14 +130,16 @@
 	#
 	# You can select the features that are enabled by default for new
 	# new users.
+	defaultFeatures: defaultFeatures =
+		collaborators: -1
+		dropbox: true
+		versioning: true
+
 	plans: plans = [{
 		planCode: "personal"
 		name: "Personal"
 		price: 0
-		features:
-			collaborators: -1
-			dropbox: true
-			versioning: true
+		features: defaultFeatures
 	}]

 	# Spelling languages


fix bug in footnote

fix bug in footnote

--- src/compiler/directives/footnote.coffee
+++ src/compiler/directives/footnote.coffee
@@ -27,7 +27,8 @@
 FootnoteContent.transform = (compiler,input,cb) ->
   footnotes = compiler.data["footnotes"] || []
   if footnotes.length == 0
-    return input
+    cb null, input
+    return

   html = """
   <ol class="footnotes">


Use router Link to prevent full page reload

--- src/components/buttons/back-button.cjsx
+++ src/components/buttons/back-button.cjsx
@@ -9,15 +9,18 @@
   displayName: 'BackButton'

   propTypes:
+    bsStyle: React.PropTypes.string
     fallbackLink: React.PropTypes.shape(
       to: React.PropTypes.string
       params: React.PropTypes.object
       text: React.PropTypes.string
     ).isRequired

+  getDefaultProps: ->
+    bsStyle: 'default'
+
   contextTypes:
     router: React.PropTypes.func
-

   render: ->
     # Gets route to last path that was not the same as the current one
@@ -32,8 +35,8 @@
       @props.fallbackLink.to, @props.fallbackLink.params
     )

-    <BS.Button href={href} className={className} {...@props}>
+    <Link className={"btn btn-#{@props.bsStyle}"} to={href}>
       {backText}
-    </BS.Button>
+    </Link>

 module.exports = BackButton


FormModal: Renamed a missing method after a refactor.

--- src/elements/FormModal/FormModal.coffee
+++ src/elements/FormModal/FormModal.coffee
@@ -70,7 +70,7 @@

 		@setData(@__orig_set_data)

-		@resetTableAndFields()
+		@removeFields()

 		CUI.Events.trigger
 			type: "data-changed"


ability to determine if an image is predominantly dark or light

for use in determining default color theme given a masthead image

--- @client/color.coffee
+++ @client/color.coffee
@@ -98,5 +98,29 @@
   hues

 window.is_light_background = (color) ->
-  color ||= customization('banner')?.background_css || DEFAULT_BACKGROUND_COLOR
+  color ||= fetch('edit_banner').background_css or customization('banner')?.background_css or DEFAULT_BACKGROUND_COLOR
   parseCssHsl(color).l > .75
+
+hsp_sample_size = 100
+window.is_image_mostly_light = (image_data, width, height) ->
+  row_sample = _.sample _.range(height), hsp_sample_size
+  light_pixels = 0
+
+  for row in row_sample
+    col_sample = _.sample _.range(width), hsp_sample_size
+    for col in col_sample
+
+      start = row * (width * 4) + col * 4
+      r = image_data[start]
+      g = image_data[start + 1]
+      b = image_data[start + 2]
+      hsp = Math.sqrt( # HSP equation from http://alienryderflex.com/hsp.html
+          0.299 * (r * r) +
+          0.587 * (g * g) +
+          0.114 * (b * b)
+        )
+      if hsp > 127.5
+        light_pixels += 1
+
+  light_pixels >= hsp_sample_size * hsp_sample_size / 2
+


Add link to new cpp

Add link to new cpp

--- server/materials/data/topics/cpp.coffee
+++ server/materials/data/topics/cpp.coffee
@@ -12,13 +12,14 @@

На самом деле даже уровень 3Б — это немного рановато для перехода на C++, вполне можно и на паскале и на питоне заниматься и на более высоких уровнях, но в любом случае, если вы добрались до сюда, то поговорите про C++ со мной. Помимо уровня, у меня есть еще один критерий: переходить на C++ имеет смысл, если вы с довольно высокой вероятностью станете призерами областной олимпиады; но, опять-таки, этот критерий не строгий.

-
К сожалению, сложно посоветовать какой-то конкретный ресурс или книгу для изучения C++. Язык довольно сложный, и есть определенный риск научиться ему неправильно, но вы уже знаете другой язык и понимаете все основные конструкции, поэтому вам будет намного проще изучать C++, чем если бы вы его учили с нуля. Попросите меня на занятии объяснить вам самые основы языка (в том числе — как работать в средах разработки под C++), можете посдавать ряд задач с уровней 1А-1Б, чтобы понять основные конструкции языка, а дальше продолжайте решать задачи на вашем текущем уровне, но на C++.

+
К сожалению, сложно посоветовать какой-то конкретный ресурс или книгу для изучения C++. Язык довольно сложный, и есть определенный риск научиться ему неправильно, но вы уже знаете другой язык и понимаете все основные конструкции, поэтому вам будет намного проще изучать C++, чем если бы вы его учили с нуля.

-
Посмотрите спецкурс из ЛКШ.2013.

+
Общая теория по C++ в предположении, что вы уже знаете какой-то другой язык (там довольно много,
+        для начала можете прочитать «по диагонали», но вообще на уровне 3Б хорошо бы уже понимать 95% того, что там написано).

-
Посмотрите "хорошие решения" на уровне 1 — там почти по каждой задаче есть "хорошее решение" на C++, а то и несколько; думаю, даже по ним можно выучить основы синтаксиса.

+
Прочитав эту теорию, можете посдавать ряд задач с уровней 1А-1Б, чтобы понять основные конструкции языка (соответственно, и теорию можете читать по частям в соответствии с темами задач), а дальше продолжайте решать задачи на вашем текущем уровне, но на C++.

-
Еще один из полезных ресурсов — материалы к лекциям Алексея Зобнина на ФКН ВШЭ. Нажимайте там на имена файлов вида 01.cpp, 02.cpp и т.д. (НЕ на надписи типа "лекция 1"), читайте код и комментарии. В первую очередь вам полезны материалы нескольких первых лекций, но для общего представления можете почитать и дальше.

+
Еще посмотрите "хорошие решения" на уровне 1 — там почти по каждой задаче есть "хорошее решение" на C++, а то и несколько; думаю, даже по ним можно выучить основы синтаксиса.

При этом важный момент: не забывайте тот язык программирования, на котором вы сейчас пишете, особенно если это питон! Питон очень удобен в тех задачах, где не столь строгие ограничения по времени, да и вообще он будет очень полезен вам дальше в жизни, поэтому, если вы уже пишете на питоне, то не забывайте его, и используйте попеременно C++ и питон для задач, выбирая, какой язык лучше, для каждой задачи отдельно. Если вы пишете на паскале, то в принципе нет большой необходимости поддерживать этот навык, но все равно лучше его не забывать, особенно на случай, если вдруг у вас какая-то задача не будет решаться на C++.


A place to stand and shoot skree while it falls

--- src/javascript/game/map/map_data.coffee
+++ src/javascript/game/map/map_data.coffee
@@ -8,7 +8,7 @@

   [ 0x00,null,null,null, null,null,null,null, null,null,null,null, null,null,null,null ]
   [ 0x00,null,0x00,null, null,null,null,null, null,null,null,null, null,null,null,null ]
-  [ 0x00,null,0x00,null, null,null,null,null, null,null,null,null, null,null,null,null ]
+  [ 0x00,null,0x00,null, null,null,0x00,0x00, null,null,null,null, null,null,null,null ]
   [ 0x00,null,0x00,0x00, null,null,null,null, null,null,null,null, null,null,null,null ]

   [ 0x00,null,null,null, null,null,null,null, null,null,null,null, null,null,0x00,null ]


Add Open in Atom button to installed packages

Closes #69

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -36,6 +36,7 @@
         @button outlet: 'uninstallButton', class: 'btn btn-default icon icon-trashcan', 'Uninstall'
         @button outlet: 'issueButton', class: 'btn btn-default icon icon-bug', 'Report Issue'
         @button outlet: 'readmeButton', class: 'btn btn-default icon icon-book', 'Open README'
+        @button outlet: 'openButton', class: 'btn btn-default icon icon-link-external', 'Open in Atom'

       @div outlet: 'errors'

@@ -106,6 +107,10 @@
           break
       false

+    @openButton.on 'click', =>
+      atom.open(pathsToOpen: [@pack.path]) if fs.existsSync(@pack.path)
+      false
+
   updateEnablement: ->
     if atom.packages.isPackageDisabled(@pack.name)
       @disableButton.text('Enable')


Fix displayName inconsistancy

Fix displayName inconsistancy

--- index.cjsx
+++ index.cjsx
@@ -107,7 +107,7 @@

 module.exports =
   name: 'Senka Calc'
-  displayName: <FontAwesome key={0} name='child' style={fontSize: 16, width: 17} /> {__ 'Senka Calc'}
+  displayName: <FontAwesome key={0} name='child' /> {__ 'Senka Calc'}
   priority: 7
   author: 'Rui'
   link: 'https://github.com/ruiii'


Correct source indentation.

Correct source indentation.

--- lib/token/row-token-parser.coffee
+++ lib/token/row-token-parser.coffee
@@ -120,8 +120,8 @@
   dataLength = buffer.readUInt16LE()

   if dataLength == NULL
-      value: undefined
-      isNull: true
+    value: undefined
+    isNull: true
   else
     value: buffer.readString(dataLength, encoding)
     isNull: false


adding an additional 10px to overflow becuase windows font rendering is way different

--- src/scripts/widgets/overflowMenu.coffee
+++ src/scripts/widgets/overflowMenu.coffee
@@ -57,7 +57,7 @@

     if $rightToolbar
       # add an additio 10 px to adjust for non fixed width fonts
-      newWidth = $this.parent().width() - $rightToolbar.width() - 10
+      newWidth = $this.parent().width() - $rightToolbar.width() - 20
       $this.css 'margin-right', 0
       $this.css 'width', newWidth


must init display defaults on demand, init is potentially too late

--- bokehjs/src/coffee/common/has_parent.coffee
+++ bokehjs/src/coffee/common/has_parent.coffee
@@ -39,9 +39,10 @@
     initialize: (attrs, options) ->
       super(attrs, options)
       @_parent = HasProperties.prototype.get.apply(this, ['parent'])
-      @_display_defaults = @display_defaults()

     get: (attr) ->
+      if not @_display_defaults
+        @_display_defaults = @display_defaults()
       if attr == 'parent'
         return @_parent
       val = super(attr)
@@ -53,6 +54,7 @@
           return val
       return @_display_defaults[attr]

-    display_defaults: -> {}
+    display_defaults: () ->
+      return {}

   return HasParent


#2904 - fixed a bug when a table was sorted by a column other than the index then edited.

This changes the semantics of getItem/setItem to use an offset into the data array, while getField/setField still take an item index.  The index and the offset will be the same for a data array that is sorted by the index field and has not been filtered.

--- bokehjs/src/coffee/widget/data_table.coffee
+++ bokehjs/src/coffee/widget/data_table.coffee
@@ -21,26 +21,28 @@

   getLength: () -> @source.get_length()

-  getItem: (index) ->
-    item = {index: index}
+  getItem: (offset) ->
+    item = {}
     for field in @fields
-      item[field] = @data[field][index]
+      item[field] = @data[field][offset]
     return item

-  _setItem: (index, item) ->
+  _setItem: (offset, item) ->
     for field, value of item
-      @data[field][index] = value
+      @data[field][offset] = value
     return

-  setItem: (index, item) ->
-    @_setItem(index, item)
+  setItem: (offset, item) ->
+    @_setItem(offset, item)
     @updateSource()

   getField: (index, field) ->
-    return @data[field][index]
+    offset = @data["index"].indexOf(index)
+    return @data[field][offset]

   _setField: (index, field, value) ->
-    @data[field][index] = value
+    offset = @data["index"].indexOf(index)
+    @data[field][offset] = value
     return

   setField: (index, field, value) ->


QUnit configuration.

QUnit configuration.

--- src/test/coffeescript/testConfig.coffee
+++ src/test/coffeescript/testConfig.coffee
@@ -1,4 +1,13 @@
 require.config
 	deps: ['/web/js/config.js'] # testConfig will add test configuration to the base configuration

-console.log('here')
+	paths:
+		qUnit: '/test-web/js/libs/qunit/qunit.js'
+
+	shim:
+		qUnit:
+			exports: 'QUnit'
+			init: ->
+				QUnit.config.autoload = false
+				QUnit.config.autostart = false
+


xterm fitting and resizing

xterm fitting and resizing

--- lib/term-view.coffee
+++ lib/term-view.coffee
@@ -4,6 +4,7 @@
 path = require 'path'
 debounce = require 'debounce'
 Terminal = require 'xterm'
+Terminal.loadAddon('fit')
 {CompositeDisposable} = require 'atom'
  # see https://github.com/f/atom-term.js/pull/5
  # see https://github.com/f/atom-term.js/pull/4
@@ -76,11 +77,8 @@
     {cols, rows, cwd, shell, shellArguments, shellOverride, runCommand, colors, cursorBlink, scrollback} = @opts
     args = shellArguments.split(/\s+/g).filter (arg) -> arg

-    if @opts.forkPTY
-      {cols, rows} = @getDimensions_()
-
     @term = term = new Terminal {
-      colors, cursorBlink, scrollback, cols, rows
+      colors, cursorBlink, scrollback
     }

     term.on "data", (data) =>
@@ -107,6 +105,8 @@
       @emitter.emit 'did-change-title', title

     term.open this.get(0)
+    term.fit()
+    { cols, rows } = @getDimensions

     if not @opts.forkPTY
       term.end = => @exit()
@@ -132,13 +132,12 @@

   resize: (cols, rows) ->
     return unless @term
-    return if @term.rows is rows and @term.cols is cols
     return unless cols > 0 and rows > 0 and isFinite(cols) and isFinite(rows)
     # console.log @term.rows, @term.cols, "->", rows, cols
     try
       if @ptyProcess
         @ptyProcess.send {event: 'resize', rows, cols}
-      if @term
+      if @term and not (@term.rows is rows and @term.cols is cols)
         @term.resize cols, rows
     catch error
       console.error error
@@ -216,26 +215,14 @@
     @term.focus()

   resizeToPane_: ->
-    return unless @ptyProcess
-    {cols, rows} = @getDimensions_()
+    return unless @ptyProcess and @term
+    @term.fit()
+    {cols, rows} = @getDimensions()
     @resize cols, rows

   getDimensions: ->
     cols = @term.cols
     rows = @term.rows
-    {cols, rows}
-
-  getDimensions_: ->
-    if not @term
-      cols = Math.floor @width() / 7
-      rows = Math.floor @height() / 15
-      return {cols, rows}
-
-    @find('.terminal').append @fakeRow
-    fakeCol = @fakeRow.children().first()
-    cols = Math.floor (@width() / fakeCol.width()) or 9
-    rows = Math.floor (@height() / fakeCol.height()) or 16
-    @fakeRow.remove()
     {cols, rows}

   exit: ->


Change to 'and' to align with CoffeeScript styleguide.

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -213,7 +213,7 @@
         else if entry instanceof DirectoryView
           entry.toggleExpansion(isRecursive)
       when 2
-        if entry instanceof FileView & @openedItem?
+        if entry instanceof FileView and @openedItem?
           @openedItem.then (item) ->
             activePane = atom.workspace.getActivePane()
             if activePane?.getPendingItem?


Whoops, forgot that Atom 1.7.0 hasn't been released yet

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -1,5 +1,6 @@
 path = require 'path'
-{shell} = require 'electron'
+# TODO: Remove the catch once Atom 1.7.0 is released
+try {shell} = require 'electron' catch then shell = require 'shell'

 _ = require 'underscore-plus'
 {BufferedProcess, CompositeDisposable} = require 'atom'


Fix syntax error

Fix syntax error

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -274,7 +274,7 @@
       platform = process.platform
       isWindows = /^win/.test(platform)
       isOSX = /^darwin/.test(platform)
-      showInExplorer("/select,#{entry.getPath()}", "explorer")) if isWindows
+      showInExplorer("/select,#{entry.getPath()}", "explorer") if isWindows
       showInFinder(entry.getPath(), { R: true }) if isOSX
     catch error
       atom.confirm


header and body are functions.

header and body are functions.

--- test/e2e/subjectDetailSpec.coffee
+++ test/e2e/subjectDetailSpec.coffee
@@ -5,6 +5,8 @@
 Page = require './helpers/page'

 class SubjectDetailPage extends Page
+  #constructor: ->
+
   # @returns a promise which resolves to the image profile button
   #   ElementFinder
   imageProfileFormatButton: ->
@@ -27,86 +29,37 @@

   # @returns the clinical profile panel promise
   clinicalProfile: ->
-    # @param selector the selection criterion
-    # @param bindings the table field {property: binding} bindings
-    # @returns the target table augmented with the component table
-    #   access functions, or null if the table was not found
-    findTableWithBindings = (selector, bindings) =>
-      # Gets the given WebElement text.
-      text = (elt) -> elt.getText() if elt
-
-      # Searches for a WebElement and resolves to its text.
-      findText = (selector) => @find(selector).then(text)
-
-      # @param table the target table
-      # @param bindings the table field bindings
-      addTextFields = (table, bindings) =>
-        # @param property the field property
-        # @param _binding the subject property bound to the field
-        addTextField = (property, binding) ->
-          # Associate the field property with its text.
-          table[property] = ->
-            # Note: the assignments below must be made locally in
-            # this anonymous function rather than preceding this
-            # statement in the addTextField function in order to
-            # work around the following Javascript scope/closure
-            # interaction anomaly:
-            # * If the local variables below are bound in the
-            #   parent context, then every property finder resolves
-            #   to the last property binding.
-            sbjBinding = 'subject.' + binding
-            # Make the field element search selector.
-            selector = By.binding(sbjBinding)
-            findText(selector)
-
-        addTextField(prop, binding) for prop, binding of bindings
-
-      # Find the table.
-      #
-      # Note: A better solution is to perform the field selects below
-      # against the table rather than the page. However,
-      # panel.findElement('qi-demographics-table')
-      # results in an error, since the untyped panel object has most, but
-      # not all, of the WebElement function properties. In particular, it
-      # is missing the findElement function. The work-around is to search
-      # in the page context instead.
-      #
-      # TODO - find out why the panel is neither an ElementFinder nor a
-      # WebElement and replace the select below with panel.element or
-      # panel.findElement. Add panel to the clinicalProfile function
-      # arguments.
-      @find(selector).then (table) ->
-        # Add the fields.
-        addTextFields(table, bindings) if table
-        # Return the table, or null if the table was not found.
-        table
-
-    # Adds a function property for each component table to the given
-    # profile panel.
+    # The table specifications as described in the getTable()
+    # function below.
     #
-    # @param panel the clinical profile panel
-    addComponentTables = (panel) ->
-      componentTables =
-        demographics:
-          selector: '#demographics'
-          bindings:
-            age: 'birthDate'
-            weight: 'weight'
-            races: 'races'
-            ethnicity: 'ethnicity'
-
-      addComponentTable = (prop, selector, bindings) ->
-        panel[prop] = -> findTableWithBindings(selector, bindings)
-
-      for prop, spec of componentTables
-        addComponentTable(prop, spec.selector, spec.bindings)
-
-    # Find the clinical profile panel.
-    @find('.qi-clinical-profile').then (panel) =>
-      # Add the component table functions.
-      addComponentTables(panel) if panel?
-      panel
-
+    TABLES =
+      demographics:
+        selector: '#demographics'
+        bindings:
+          age: 'subject.birthDate'
+          weight: 'subject.weight'
+          races: 'subject.races'
+          ethnicity: 'subject.ethnicity'
+
+    @find('.qi-clinical-profile').then (profile) ->
+      if profile?
+        # The table object builder.
+        getTable = (name) ->
+          spec = TABLES[name]
+          profile.findTable(spec.selector).then (table) ->
+            table.addBindings(spec.bindings) if table?
+
+        # The table accessor properties.
+        for name in Object.keys(TABLES)
+          profile[name] = getTable(name)
+
+        # The encounter accessor properties.
+        for enc in ['Biopsy', 'Surgery', 'Assessment']
+          profile[enc.toLowerCase()] =
+            profile.find("div[ng-switch-when='#{ enc }']")
+
+      # Return the clinical profile.
+      profile

 describe 'E2E Testing Subject Detail', ->
   page = null
@@ -195,9 +148,9 @@
         before ->
           # The tables are only added to the DOM if the format button
           # is clicked.
-          modelingTables = page.imageProfileFormatButton().then (button) ->
+          page.imageProfileFormatButton().then (button) ->
             button.click().then ->
-              page.modelingTables()
+              modelingTables = page.modelingTables()

         after ->
           # Restore the format to chart.
@@ -206,55 +159,65 @@

         # There are three modeling tables, one for each PK modeling
         # property.
-        it 'should have three tables', ->
+        it 'should have three modeling parameter tables', ->
           modelingTables.then (tables) ->
             # The modeling tables exists.
-            expect(tables, 'The modeling tables are missing')
+            expect(tables, 'The modeling parameter tables are missing')
               .to.exist.and.not.be.empty
-            expect(tables.length, 'The modeling table count is incorrect')
+            expect(tables.length,
+                   'The modeling parameter table count is incorrect')
               .to.equal(3)

         describe 'Properties', ->
           it 'should display the column headers', ->
             modelingTables.then (tables) ->
               for table, tblNdx in tables
-                table.header.then (headings) ->
+                table.header().then (headings) ->
                   for heading, hdgNdx in headings
                     expect(heading, "Table #{ tblNdx + 1 } is missing column" +
                                     " heading #{ hdgNdx + 1 }")
                       .to.exist.and.not.be.empty
-
+
           it 'should have four Breast patient visits', ->
             modelingTables.then (tables) ->
               for table, tblNdx in tables
-                table.body.then (rows) ->
+                table.body().then (rows) ->
                   expect(rows.length,
                          "Table #{ tblNdx + 1 } row count is incorrect")
                     .to.equal(4)
-
+
           it 'should show the visit dates', ->
             modelingTables.then (tables) ->
-              # The expected visit dates are the first column of the first table.
+              # The expected visit dates are the first column of the first
+              # table.
               referenceTable = _.first(tables)
               expected = null
-              referenceTable.body.then (rows) ->
-                expected = ((_.first(columns) for columns in rows))
-              # The date values for every remaining table should be
-              # consistent with those of the first table.
-              for table, tblNdx in _.rest(tables)
-                table.body.then (rows) ->
-                  for columns, rowNdx in rows
-                    visitDate = _.first(columns)
-                    expect(visitDate,
-                           "The table #{ tblNdx + 2 } visit date is incorrect")
-                      .to.equal(expected[rowNdx])
-
+              referenceTable.body().then (rows) ->
+                expected = (_.first(columns) for columns in rows)
+                for date, rowNdx in expected
+                  expect(date,
+                         "The table 1 row #{ rowNdx + 1 } visit date is missing")
+                    .to.exist
+                # The date values for every remaining table should be
+                # consistent with those of the first table.
+                for table, tblNdx in _.rest(tables)
+                  table.body().then (rows) ->
+                    for columns, rowNdx in rows
+                      date = _.first(columns)
+                      expect(date,
+                             "The table #{ tblNdx + 2 } row #{ rowNdx + 1 }" +
+                             " visit date is missing")
+                        .to.exist
+                      expect(date,
+                             "The table #{ tblNdx + 2 } row #{ rowNdx + 1 }" +
+                             " visit date is incorrect")
+                        .to.equal(expected[rowNdx])
+
           it 'should have seven Ktrans table columns', ->
             modelingTables.then (tables) ->
-
               # The KTrans table is the first table.
               table = _.first(tables)
-              table.body.then (rows) ->
+              table.body().then (rows) ->
                 for row, rowNdx in rows
                   expect(row.length, "The Ktrans table row #{ rowNdx + 1 }" +
                                      " column count is incorrect").to.equal(7)
@@ -264,7 +227,7 @@
               # The KTrans table is the first table.
               tables = _.rest(tables)
               for table, tblNdx in tables
-                table.body.then (rows) ->
+                table.body().then (rows) ->
                   for row, rowNdx in rows
                     expect(row.length,
                            "The table #{ tblNdx + 2 } row #{ rowNdx + 1 }" +
@@ -273,7 +236,7 @@
           it 'should not have a blank non-percent change value', ->
             modelingTables.then (tables) ->
               for table, tblNdx in tables
-                table.body.then (rows) ->
+                table.body().then (rows) ->
                   for columns, rowNdx in rows
                     # The odd columns hold non-percent change values.
                     for colNdx in _.range(1, columns.length, 2)
@@ -286,7 +249,7 @@
           it 'should have blank percent values in the first row', ->
             modelingTables.then (tables) ->
               for table, tblNdx in tables
-                table.body.then (rows) ->
+                table.body().then (rows) ->
                   columns = _.first(rows)
                   expect(columns, "Imaging Profile table #{ tblNdx + 1 } body" +
                                   " row 0 has no columns")
@@ -303,7 +266,7 @@
           it 'should not have a blank percent value in the remaining rows', ->
             modelingTables.then (tables) ->
               for table, tblNdx in tables
-                table.body.then (rows) ->
+                table.body().then (rows) ->
                   for columns, rowNdx in _.rest(rows)
                     for colNdx in _.range(2, columns.length, 2)
                       value = columns[colNdx]
@@ -319,7 +282,7 @@
         clinicalProfile = page.clinicalProfile()

       it 'should show the clinical profile', ->
-        expect(clinicalProfile, 'The clinical profile is missing')
+        expect(clinicalProfile, 'The Breast clinical profile is missing')
           .to.eventually.exist

       describe 'Demographics', ->
@@ -327,18 +290,18 @@

         before ->
           demographics = clinicalProfile.then (profile) ->
-            profile.demographics()
+            profile.demographics

         it 'should show the demographics table', ->
           demographics.then (table) ->
             expect(table, 'The demographics table is missing').to.exist
             expect(table.isDisplayed(), 'The demographics table is not displayed')
               .to.eventually.be.true
-
+
         # Validate the demographics values.
         it 'should show the age', ->
           demographics.then (table) ->
-            table.age().then (age) ->
+            table.age.then (age) ->
               expect(age, 'The age is missing').to.exist.and.to.not.be.empty
               expect(age, 'The age is not an integer').to.match(/^\d+$/)
               expect(parseInt(age), 'The age is not positive')
@@ -346,24 +309,25 @@

         it 'should show the weight', ->
           demographics.then (table) ->
-            table.weight().then (weight) ->
+            table.weight.then (weight) ->
               expect(weight, 'The weight is missing').to.exist.and.to.not.be.empty
               expect(weight, 'The weight is not an integer').to.match(/^\d+$/)
               expect(parseInt(weight), 'The weight is not positive')
                 .to.be.greaterThan(0)
-
+
         it 'should show the race', ->
           demographics.then (table) ->
-            table.races().then (races) ->
+            table.races.then (races) ->
               expect(races, 'The race is missing').to.exist.and.to.not.be.empty
-
+
         it 'should show the ethnicity', ->
           demographics.then (table) ->
-            table.ethnicity().then (ethnicity) ->
+            table.ethnicity
+            .then (ethnicity) ->
               expect(ethnicity, 'The ethnicity is missing')
                 .to.exist.and.to.not.be.empty

-      describe 'Outcomes', ->
+      describe 'Encounters', ->
           describe 'Biopsy', ->
             # TODO - Add test cases for the date and every outcome table.

@@ -371,20 +335,50 @@
             # TODO - Add test cases for the date and every outcome table.

   describe 'Sarcoma', ->
+    page = null
+
     beforeEach ->
       page = new SubjectDetailPage '/quip/sarcoma/subject/1?project=QIN_Test'

     it 'should load the page', ->
       expect(page.content, 'The page was not loaded').to.eventually.exist
-
+
     # The Sarcoma-specific page header test cases.
     describe 'Header', ->
       it 'should display the billboard', ->
         expect(page.billboard, 'The billboard is incorrect')
           .to.eventually.equal('Sarcoma Patient 1')

-    describe 'Biopsy', ->
-      # TODO - Add test cases for the date and every outcome table.
-
-    describe 'Assessment', ->
-      # TODO - Add test cases for the date and every outcome table.
+    describe 'Clinical Profile', ->
+      clinicalProfile = null
+
+      before ->
+        clinicalProfile = page.clinicalProfile()
+
+      it 'should show the clinical profile', ->
+        expect(clinicalProfile, 'The Sarcoma clinical profile is missing')
+          .to.eventually.exist
+
+      describe 'Encounters', ->
+        describe 'Biopsy', ->
+          biopsy = null
+
+          before ->
+            biopsy = clinicalProfile.then (profile) ->
+              profile.find('div[ng-switch-when="Biopsy"]')
+
+          it 'should show the biopsy panel', ->
+            biopsy.then (table) ->
+              expect(table, 'The Sarcoma biopsy panel is missing').to.exist
+              expect(table.isDisplayed(), 'The Sarcoma biopsy panel is not' +
+                                          ' displayed')
+                .to.eventually.be.true
+
+          it 'should show the tumor site', ->
+            biopsy.then (table) ->
+              table.find(By.binding('pathology.location')).then (site) ->
+                expect(site, 'The Sarcoma site table is missing').to.exist
+                expect(site.isDisplayed(), 'The Sarcoma pathology site field' +
+                                           ' is not displayed')
+                  .to.eventually.be.true
+


test(config): configurable on runtime

test(config): configurable on runtime

--- test/spec/providers/config.coffee
+++ test/spec/providers/config.coffee
@@ -1,38 +1,44 @@
-describe 'configFactory', ->
+describe 'configProvider', ->

   describe 'default', ->
-
-    config = null
-    scope  = null
-
     beforeEach module 'bp'

-    beforeEach inject ($rootScope, bpConfig) ->
+    it 'should have config', inject ($rootScope, bpConfig) ->
       config = bpConfig
       scope  = $rootScope.$new()
       scope.$apply()

-    it 'should have default config', ->
       expect(config.platform).toBe 'ios'

-  describe 'user', ->
-
-    config = null
-    scope  = null
+  describe 'provider', ->

     object = {foo:1}

     beforeEach module 'bp', (bpConfigProvider) ->
       bpConfigProvider.setConfig
-        property: 'random'
+        bar: 'foo'
         object: object
-      null
+      return

-    beforeEach inject ($rootScope, bpConfig) ->
+    it 'should be configurable', inject ($rootScope, bpConfig) ->
       config = bpConfig
       scope  = $rootScope.$new()
       scope.$apply()

-    it 'should have user config', ->
-      expect(config.property).toBe 'random'
-      expect(config.object).toEqual object
+      expect(config.bar).toBe 'foo'
+      expect(config.object).toBe object
+
+  describe 'runtime', ->
+    beforeEach module 'bp'
+
+    it 'should be configurable', inject ($rootScope, bpConfig) ->
+      config = bpConfig
+      scope  = $rootScope.$new()
+      scope.$apply()
+
+      expect(config.platform).toBe 'ios'
+
+      config.setConfig
+        foo: 'bar'
+
+      expect(config.foo).toBe 'bar'


Test for stateCycle withou arguments

Test for stateCycle withou arguments

--- test/tests/LayerStatesTest.coffee
+++ test/tests/LayerStatesTest.coffee
@@ -530,6 +530,19 @@
 						layer.states.current.name.should.equal "testA"
 						done()

+		it "should cycle all without state list", (done) ->
+			layer = new Layer
+			layer.animationOptions.time = 0.1
+			layer.states.testA = {x: 200}
+			layer.states.testB = {x: 400}
+			layer.stateCycle onEnd: ->
+				layer.states.current.name.should.equal "testA"
+				layer.stateCycle onEnd: ->
+					layer.states.current.name.should.equal "testB"
+					layer.stateCycle onEnd: ->
+						layer.states.current.name.should.equal "default"
+						done()
+
 	describe "Switch", ->

 		it "should switch", ->


change console.log for logger.log

change console.log for logger.log

--- services/clsi/app/coffee/DbQueue.coffee
+++ services/clsi/app/coffee/DbQueue.coffee
@@ -1,12 +1,12 @@
 async = require "async"
 Settings = require "settings-sharelatex"
-
+logger = require("logger-sharelatex")
 queue = async.queue((task, cb)->
 		task(cb)
 	, Settings.parallelSqlQueryLimit)

 queue.drain = ()->
-    console.log('HI all items have been processed')
+	logger.debug('all items have been processed')

 module.exports =
 	queue: queue


eliminate utterance queuing

eliminate utterance queuing

--- documentation/coffee/async.coffee
+++ documentation/coffee/async.coffee
@@ -6,6 +6,7 @@
   for i in [seconds..1]
     if window.speechSynthesis?
       utterance = new SpeechSynthesisUtterance "#{i}"
+      window.speechSynthesis.cancel() # cancel any prior utterances
       window.speechSynthesis.speak utterance
     console.log i
     await sleep 1000 # wait one second


OX-137 console output that illustrates the problem, no solution yet

--- app/assets/javascripts/input.coffee
+++ app/assets/javascripts/input.coffee
@@ -50,7 +50,7 @@

     KeyboardJS.bind.key(
       key
-      (evt) =>
+      (evt, keys, key2) =>
         # When first pressed, insert the callback into
         # keyCallbackMap and start the buttonLoop.
         # Then, ignore any other events fired from the operating
@@ -58,6 +58,7 @@
         # When control key is pressed, everything is ignored, because
         # if there is any browser action attached to this (as with Ctrl + S)
         # KeyboardJS does not receive the up event.
+        console.log key2 + " down"
         unless @keyCallbackMap[key]?
           if not evt.ctrlKey
             @keyPressedCount++
@@ -65,7 +66,12 @@
             @buttonLoop() if @keyPressedCount == 1

         return
-      =>
+      (evt, keys, key2) =>
+        activeKeys = KeyboardJS.activeKeys()
+        console.log key2 + " up"
+        #for key of @keyCallbackMap
+        #  if activeKeys.indexOf(key) < 0
+        #    delete @keyCallbackMap[key]
         if @keyCallbackMap[key]?
           @keyPressedCount--
           delete @keyCallbackMap[key]


no katex rendering inside form controls

no katex rendering inside form controls

--- app/assets/javascripts/katex.coffee
+++ app/assets/javascripts/katex.coffee
@@ -22,7 +22,7 @@
         display: true
       }
     ]
-    ignoredClasses: ['trix-content']
+    ignoredClasses: ['trix-content', 'form-control']
     throwOnError: false

   return


Added search function and fixed camera controls

--- app/assets/javascripts/posts.coffee
+++ app/assets/javascripts/posts.coffee
@@ -37,10 +37,15 @@
     else if $(window).width() < 768
       console.log("three rows")

-  $('#search-box-field').on 'change keyup paste mouseup', ->
+  $('#search-box-field').on 'change keyup keydown paste mouseup', (event) ->
+    key = event.keyCode or event.which
+    if key == 13
+      event.preventDefault()
+      return
     if $(this).val() != lastSearchBoxValue
       lastSearchBoxValue = $(this).val()
       console.log 'The text box changed'
+      search(lastSearchBoxValue)
     return

   $('#up_button').click ->
@@ -69,6 +74,9 @@
     canvas_container_id: "b4w-overlay",
     callback: init_cb
   })
+
+search = (text) ->
+  console.log("search for: " + text)

 change_floor = (direction) ->
   console.log("Current floor: "  + current_floor.toString())
@@ -173,7 +181,7 @@
   document.getElementById("b4w-overlay").addEventListener("touchend", touch_end_cb, false)

 load_cb = ->
-  m_app.enable_camera_controls()
+  m_app.enable_camera_controls(false, true)
   camera = m_scenes.get_active_camera();
   pivot_distance = m_cam.target_get_distance(camera)
   console.log("Platform name: " + platform.name)


Increase limit of repos to 200 per org

Increase limit of repos to 200 per org

--- app/coffee/RepositoryManager.coffee
+++ app/coffee/RepositoryManager.coffee
@@ -49,10 +49,10 @@
 		ghclient.me().orgs callback

 	_getOrgRepos: (ghclient, org, callback = (error, repos) ->) ->
-		ghclient.org(org).repos type: "public", callback
+		ghclient.org(org).repos type: "public", page: 1, per_page: 200, callback

 	_getPersonalRepos: (ghclient, callback = (error, repos) ->) ->
-		ghclient.me().repos type: "public", callback
+		ghclient.me().repos type: "public", page: 1, per_page: 200, callback

 	saveWebHook: (repo, id, callback = (error) ->) ->
 		db.githubRepos.update({


init notebooks

init notebooks

--- app/js/controllers/notebooks.coffee
+++ app/js/controllers/notebooks.coffee
@@ -40,8 +40,6 @@
         # console.log 'blocktime', Info.info.last_block_time
         # console.log time
         $scope.next_drawing_time = time
-
-    next_drawing_time()

     # fetch last 7 round winners
     $scope.getRecentWinners = (rounds = 7) ->
@@ -99,6 +97,11 @@
                             if key_maps[winner.reward_owner]
                               winner.reward_owner = key_maps[winner.reward_owner]

-        # console.log 'recent_winners', $scope.recent_winners
+    init = ->
+      next_drawing_time()
+      $scope.getRecentWinners()

-    $scope.getRecentWinners()+    unless Info.symbol != ""
+      Info.refresh_info().then -> init()
+    else
+      init()


fix metadata refresh

fix metadata refresh

--- src/cs-resource/cs-resource.js.coffee
+++ src/cs-resource/cs-resource.js.coffee
@@ -81,8 +81,13 @@
         (data) =>  # successCallback
           objects   = _.map data.data, ((i) => new @(i, datastore: datastore))
           included  = _.map data.included, (i) =>
-            resource = ResourceService.get(i.type)
-            return new resource(i, datastore: datastore)
+            assoc = datastore.get(i.type, i.id)
+            if assoc
+              assoc.$assign(i)
+              return assoc
+            else
+              resource = ResourceService.get(i.type)
+              return new resource(i, datastore: datastore)
           return objects
         (reason) => # errorCallback
           return $q.reject(reason);
@@ -199,6 +204,7 @@
     $assign: (value_object) ->
       # this = value_object, while keeping the existing attributes if they exist
       delete @relationships
+      delete @meta
       angular.merge(@, _.pick(value_object, "id", "type", "attributes", "relationships", "links", "meta"))

       if value_object.$datastore
@@ -223,6 +229,7 @@
       delete @attributes
       delete @relationships
       delete @links
+      delete @meta
       angular.merge(@, _.pick(value_object, "id", "type", "attributes", "relationships", "links", "meta"))
       @.$datastore = new csDataStore(value_object.$datastore)


spike of limit

spike of limit

--- services/web/app/coffee/models/Project.coffee
+++ services/web/app/coffee/models/Project.coffee
@@ -74,13 +74,26 @@
 		elementType = "fileRefs"
 	return elementType

+countElements = (project, callback)->
+
+	countFolder = (folder, cb)->
+		jobs = _.map folder?.folders, (folder)->
+			(asyncCb)-> countFolder folder, asyncCb
+		async.series jobs, (err, results)->
+			total = _.reduce results, (a, b)-> return a+b
+			total += folder?.docs?.length
+			total += folder?.fileRefs?.length
+			cb(null, subTotal)
+
+	countFolder project.rootFolder[0], callback
+
 ProjectSchema.statics.putElement = (project_id, folder_id, element, type, callback)->
 	if !element?
 		e = new Error("no element passed to be inserted")
 		logger.err project_id:project_id, folder_id:folder_id, element:element, type:type, "failed trying to insert element as it was null"
 		return callback(e)
 	type = sanitizeTypeOfElement type
-	require('../Features/Project/ProjectGetter').getProjectWithOnlyFolders project_id, (err, project)=>
+	require('../Features/Project/ProjectGetter').getProject project_id, "rootFolder", (err, project)=>
 		if err?
 			callback(err)
 		if !folder_id?
@@ -100,7 +113,7 @@
 			update["$push"][mongopath] = element
 			this.update conditions, update, {}, (err)->
 				if(err)
-					logger.err err: err, project: project, 'error saving in putElement project'
+					logger.err err: err, project_id: project_id, 'error saving in putElement project'
 				if callback?
 					callback(err, {path:newPath})


Removed redundant code

Removed redundant code

--- public/coffee/views/appView.coffee
+++ public/coffee/views/appView.coffee
@@ -6,15 +6,6 @@
     databases.bind 'add', this.render
     databases.bind 'reset', this.render
     databases.fetch()
-
-  # addDatabase: (database) =>
-  #   id = database.get('_id')
-  #   html = this.databaseTemplate { id: id, name: database.get('name'), url: "databases/#{id}" }
-  #   $("#databaseList").append(html)
-
-  # refreshDatabases: =>
-  #   console.log 'AppView#refreshDatabases'
-  #   databases.each(this.addDatabase)

   render: =>
     console.log 'AppView#render'


Github Star Position Fix #homepage

Github Star Position Fix #homepage

--- docs/components/home/HomeBody.cjsx
+++ docs/components/home/HomeBody.cjsx
@@ -26,7 +26,7 @@

       star:
         position: 'absolute'
-        right: '-35px'
+        right: '-23px'
         zIndex: '2'
         top: '9px'


Click was not opening tag routs.

Click was not opening tag routs.

--- client/app/CommonViews/Tags/TagViews.coffee
+++ client/app/CommonViews/Tags/TagViews.coffee
@@ -88,6 +88,15 @@
   pistachio:->
     super "{{#(title)}}"

+  click:(e)->
+    e?.preventDefault()
+    e?.stopPropagation()
+    @openTag @getData()
+
+  openTag:(tag)->
+    {entryPoint} = KD.config
+    KD.getSingleton('router').handleRoute "/Topics/#{tag.slug}", {state:tag, entryPoint}
+
 #  click:(event)->
 #    event?.stopPropagation()
 #    event?.preventDefault()


Better management of IMAP and SMTP ports and options

--- client/app/components/account-config.coffee
+++ client/app/components/account-config.coffee
@@ -88,7 +88,6 @@
         return should

     render: ->
-        console.log "Render", @props.error, @state.errors
         if @props.selectedAccount?
             titleLabel = t "account edit"
         else
@@ -224,6 +223,8 @@
                             valueLink: @linkState('smtpPort'),
                             type: 'text',
                             className: 'form-control'
+                            onBlur: @_onSMTPPort,
+                            onInput: => @setState(smtpManualPort: true)
                     getError 'smtpServer'
                     getError 'smtpPort'
                 div className: 'form-group',
@@ -234,9 +235,10 @@
                     div className: 'col-sm-1',
                         input
                             id: 'mailbox-smtp-ssl',
-                            valueLink: @linkState('smtpSSL'),
+                            checkedLink: @linkState('smtpSSL'),
                             type: 'checkbox',
                             className: 'form-control'
+                            onClick: (ev) => @_onServerParam ev.target, 'smtp', 'ssl'
                     label
                         htmlFor: 'mailbox-smtp-tls',
                         className: 'col-sm-2 control-label',
@@ -244,9 +246,10 @@
                     div className: 'col-sm-1',
                         input
                             id: 'mailbox-smtp-tls',
-                            valueLink: @linkState('smtpTLS'),
+                            checkedLink: @linkState('smtpTLS'),
                             type: 'checkbox',
                             className: 'form-control'
+                            onClick: (ev) => @_onServerParam ev.target, 'smtp', 'tls'

             fieldset null,
                 legend null, t 'account receiving server'
@@ -271,7 +274,9 @@
                             id: 'mailbox-imap-port',
                             valueLink: @linkState('imapPort'),
                             type: 'text',
-                            className: 'form-control'
+                            className: 'form-control',
+                            onBlur: @_onIMAPPort
+                            onInput: => @setState(imapManualPort: true)
                     getError 'imapServer'
                     getError 'imapPort'
                 div className: 'form-group',
@@ -282,9 +287,10 @@
                     div className: 'col-sm-1',
                         input
                             id: 'mailbox-imap-ssl',
-                            valueLink: @linkState('imapSSL'),
+                            checkedLink: @linkState('imapSSL'),
                             type: 'checkbox',
                             className: 'form-control'
+                            onClick: (ev) => @_onServerParam ev.target, 'imap', 'ssl'
                     label
                         htmlFor: 'mailbox-imap-tls',
                         className: 'col-sm-2 control-label',
@@ -292,9 +298,10 @@
                     div className: 'col-sm-1',
                         input
                             id: 'mailbox-imap-tls',
-                            valueLink: @linkState('imapTLS'),
+                            checkedLink: @linkState('imapTLS'),
                             type: 'checkbox',
                             className: 'form-control'
+                            onClick: (ev) => @_onServerParam ev.target, 'imap', 'tls'

             div className: 'form-group',
                 div className: 'col-sm-offset-2 col-sm-5 text-right',
@@ -454,13 +461,73 @@
                     infos.imapPort   = '993'
                 if not infos.smtpServer?
                     infos.smtpServer = ''
-                    infos.smtpPort   = '587'
+                    infos.smtpPort   = '465'
+                switch infos.imapPort
+                    when '993'
+                        infos.imapSSL = true
+                        infos.imapTLS = false
+                    else
+                        infos.imapSSL = false
+                        infos.imapTLS = false
+                switch infos.smtpPort
+                    when '465'
+                        infos.smtpSSL = true
+                        infos.smtpTLS = false
+                    when '587'
+                        infos.smtpSSL = false
+                        infos.smtpTLS = true
+                    else
+                        infos.smtpSSL = false
+                        infos.smtpTLS = false
                 @setState infos

+    _onServerParam: (target, server, type) ->
+        if (server is 'imap' and @state.imapManualPort) or
+        (server is 'smtp' and @state.smtpManualPort)
+            # port has been set manually, don't update it
+            return
+        if server is 'smtp'
+            if type is 'ssl' and target.checked
+                @setState smtpPort: 465
+            else if type is 'tls' and target.checked
+                @setState smtpPort: 587
+        else
+            if target.checked
+                @setState imapPort: 993
+            else
+                @setState imapPort: 143
+
+    _onIMAPPort: (ev) ->
+        port = ev.target.value.trim()
+        infos =
+            imapPort: port
+        switch port
+            when '993'
+                infos.imapSSL = true
+                infos.imapTLS = false
+            else
+                infos.imapSSL = false
+                infos.imapTLS = false
+        @setState infos
+
+    _onSMTPPort: (ev) ->
+        port = ev.target.value.trim()
+        infos = {}
+        switch port
+            when '465'
+                infos.smtpSSL = true
+                infos.smtpTLS = false
+            when '587'
+                infos.smtpSSL = false
+                infos.smtpTLS = true
+            else
+                infos.smtpSSL = false
+                infos.smtpTLS = false
+        console.log port, infos
+        @setState infos

     componentWillReceiveProps: (props) ->

-        console.log "Will receive", props.error, props.isWaiting, @state.errors
         # prevents the form from changing during submission
         if not props.isWaiting
             # display the account values
@@ -502,8 +569,12 @@
             init = (field) ->
                 state[field] = ''
             init field for field in @_accountFields
-            state.smtpPort = 587
+            state.smtpPort = 465
+            state.smtpSSL  = true
+            state.smtpTLS  = false
             state.imapPort = 993
+            state.imapSSL  = true
+            state.imapTLS  = false
             state.newMailboxParent = null
             state.tab = null


misc tiny fixes

misc tiny fixes

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -550,8 +550,10 @@

                 iife_dest_bb = null
                 iife_rv = null
-
-                if n.fromIIFE
+
+                fromIIFE = n.fromIIFE
+
+                if fromIIFE
                         insertBlock = ir.getInsertBlock()
                         insertFunc = insertBlock.parent

@@ -563,13 +565,14 @@
                 @visitWithScope new_scope, n.body

                 @iifeStack.pop()
-                if iife_dest_bb
+
+                if fromIIFE
+
                         ir.createBr iife_dest_bb
                         ir.setInsertPoint iife_dest_bb
                         rv = @createLoad @findIdentifierInScope(n.ejs_iife_rv.name), "%iife_rv_load"
-                        rv
-                else
-                        n
+                        return rv
+                n

         visitSwitch: (n) ->
                 insertBlock = ir.getInsertBlock()
@@ -1689,11 +1692,12 @@

                 callsite_load = ir.createLoad callsite_alloca, "load_local_callsite"

+                # this looks wrong but this is how we test for a 0 ejsval
                 isnull = @isNumber callsite_load
                 ir.createCondBr isnull, then_bb, merge_bb

                 @doInsideBBlock then_bb, =>
-                        @createCall(@ejs_runtime.gc_add_root, [callsite_global], "add_callsite_root");
+                        @createCall(@ejs_runtime.gc_add_root, [callsite_global], "");
                         callsite_obj = @visit callsite_obj_literal
                         ir.createStore callsite_obj, callsite_global
                         ir.createStore callsite_obj, callsite_alloca
@@ -1753,7 +1757,7 @@

         handleSetLocal: (exp, opencode) ->
                 dest = @findIdentifierInScope exp.arguments[0].name
-                throw new Error "identifier not found: #{exp.arguments[0].name}" if not dest?
+                reportError(ReferenceError, "identifier not found: #{exp.arguments[0].name}", @filename, exp.arguments[0].loc) if not dest?
                 arg = exp.arguments[1]
                 @storeToDest dest, arg
                 ir.createLoad dest, "load_val"


bug fix: do not overwrite done date of msgs

--- server/emailscheduler.coffee
+++ server/emailscheduler.coffee
@@ -10,10 +10,11 @@
     for userId, messages of _.groupBy (@load name),'userId'
       @send composer messages, userId, name
       #@queue.update { _id: userId }, $set: done: true
-    @remove name
-  load: (schedule) -> (@queue.find schedule: schedule, done: $exists: 0).fetch()
+    @done name
+  query: (schedule) -> schedule: schedule, done: $exists: 0
+  load: (schedule) -> (@queue.find @query schedule).fetch()
   #remove: (schedule) -> (@queue.remove { schedule: schedule }, { multi: true })
-  remove: (schedule) -> @queue.update { schedule: schedule }, { $set: done: new Date() }, multi: true
+  done: (schedule) -> @queue.update (@query schedule), { $set: done: new Date() }, multi: true
   send: (composedMessage) -> @sendEmail m.address, m.subject, m.content if (m=composedMessage)?
   add: (schedule, user, data) -> @queue.insert schedule: schedule, data: data, userId: user?._id ? user


Woops had <form> tags ending </div>

Woops had <form> tags ending </div>

--- client/coffeescript/templates/tagbox.coffee
+++ client/coffeescript/templates/tagbox.coffee
@@ -127,7 +127,8 @@
     ret += " grad" if @tags[tag]?
     ret += " mytag" if tag in @my_tags
     ret += " suggested" if context.suggested
-    ret += "'>" + tag + "
"
+    ret += "'>" + tag
+    ret += if context.suggested then "</form>" else "
"
   return ret
 )


re-enable mktscript processing

re-enable mktscript processing


IDEView: Minor code refactor.

IDEView: Minor code refactor.

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -481,9 +481,7 @@
       @emitChange pane, context: {}, 'TabChanged'

-  goToLine: ->
-
-    @getActivePaneView().aceView.ace.showGotoLine()
+  goToLine: -> @getActivePaneView().aceView.ace.showGotoLine()

   click: ->
@@ -704,9 +702,7 @@
       kd.warn "Failed to terminate session, possibly it's already dead.", err

-  dragover: (event) ->
-
-    kd.utils.stopDOMEvent event
+  dragover: (event) -> kd.utils.stopDOMEvent event

   drop: (event) ->
@@ -802,9 +798,8 @@

   toggleVisibility = (handle, state) ->
-    el = handle.getElement()
-    if state
-    then el.classList.add 'in'
+
+    if   handle.getElement() then el.classList.add 'in'
     else el.classList.remove 'in'

@@ -831,9 +826,7 @@
     return notifier

-  setHash: (hash) ->
-
-    @hash = hash or generatePassword 64, no
+  setHash: (hash) -> @hash = hash or generatePassword 64, no

   handleTabMoved: (params) ->


StatusBar: Handle collaboration started and ended phases.

--- client/ide/views/statusbar/statusbar.coffee
+++ client/ide/views/statusbar/statusbar.coffee
@@ -10,9 +10,11 @@

     @participantAvatars = {}

-    @on 'ShowAvatars',       @bound 'showAvatars'
-    @on 'ParticipantLeft',   @bound 'removeParticipantAvatar'
-    @on 'ParticipantJoined', @bound 'addParticipantAvatar'
+    @on 'ShowAvatars',          @bound 'showAvatars'
+    @on 'ParticipantLeft',      @bound 'removeParticipantAvatar'
+    @on 'ParticipantJoined',    @bound 'addParticipantAvatar'
+    @on 'CollaborationEnded',   @bound 'handleCollaborationEnded'
+    @on 'CollaborationStarted', @bound 'handleCollaborationStarted'

     @addSubView @status = new KDCustomHTMLView cssClass : 'status'
@@ -91,3 +93,11 @@
     return if @participantAvatars[nickname]

     @createParticipantAvatar nickname
+
+
+  handleCollaborationEnded: ->
+    @share.updatePartial 'Share'
+    @avatars.destroySubViews()
+
+  handleCollaborationStarted: ->
+    @share.updatePartial 'Chat'