Redirect to new derived content

Redirect to new derived content

--- src/scripts/modules/media/title/title.coffee
+++ src/scripts/modules/media/title/title.coffee
@@ -2,7 +2,6 @@
   _ = require('underscore')
   Backbone = require('backbone')
   session = require('cs!session')
-  linksHelper = require('cs!helpers/links')
   router = require('cs!router')
   EditableView = require('cs!helpers/backbone/views/editable')
   #MailPopoverView = require('cs!./popovers/mail/mail')
@@ -51,18 +50,9 @@
       @listenTo(router, 'navigate', @render)

     derive: () ->
-      ###
       options =
         success: (model) =>
-          @model.setPageNumber(@model.get('contents').indexOf(model)+1)
-          # Update the url bar path
-          href = linksHelper.getPath 'contents',
-            model: @model
-            page: @model.getPageNumber()
-          router.navigate(href, {trigger: false, analytics: true})
-
-      @model.deriveCurrentPage(options)
-      ###
+          router.navigate("/contents/#{model.id}", {trigger: true})

       # Derive a copy of the book and then navigate to it
       @model.derive(options)


fix edge case in choosing bookshelf modal

--- scripts/gh-book/auth.coffee
+++ scripts/gh-book/auth.coffee
@@ -25,7 +25,7 @@
       'click #show-diffs': 'showDiffsModal'
       'click #edit-repo': 'editRepoModal'
       'submit #edit-repo-form': 'editRepo'
-      'click [data-default-repo]': 'editRepo'
+      'click [data-default-repo]': 'defaultRepo'

     initialize: () ->
       # When a model has changed (triggered `dirty`) update the Save button
@@ -191,6 +191,14 @@
       # Show the modal
       $modal.modal {show:true}

+    defaultRepo: (e) ->
+
+      # null these out in case they typed something
+      # before clicking the link
+      @$el.find('#repo-user').val('')
+      @$el.find('#repo-name').val('')
+      @editRepo(e)
+
     # Edit the current repo settings
     editRepo: (e) ->
       # Prevent form submission
@@ -203,7 +211,7 @@

         repoUser = auth.$el.find('#repo-user').val() || config.defaultRepo.repoUser
         repoName = auth.$el.find('#repo-name').val() || config.defaultRepo.repoName
-        branchName = auth.$el.find('#repo-branch').val() # '' means default branch
+        branchName = '' # means default branch

         # First check validity of the new repo details. Do this by attempting
         # to read META-INFO/container.xml, which should exist for all real


fix cjsx error

fix cjsx error

--- src/smc-webapp/course/assignments_panel.cjsx
+++ src/smc-webapp/course/assignments_panel.cjsx
@@ -429,7 +429,7 @@
         <div style={float:'right'}>
             <SkipCopy
                 assignment = {@props.assignment}
-                step       = step
+                step       = {step}
                 actions    = {@actions(@props.name)}
             />
         </div>


avoid to write mktscript protocol when source comes from string not from file


Tidy up some spacing.

Tidy up some spacing.

--- js/flight.coffee
+++ js/flight.coffee
@@ -41,13 +41,9 @@
     if @os.android and @os.version <= "2.1" then @transitionAnimation = false

     if @hideUrlBar is true then @hideUrlBar()
-

-
   # Goes to page, transitionAnimation defines if transition happens or not
-  goto: (targetPage, options) =>
-
-
+  goto: (targetPage, options) =>

     if typeof targetPage is "string"
       @targetPage = document.querySelector targetPage


Change display style to -webkit-box according to new markup for native scrolling.

--- js/flight.coffee
+++ js/flight.coffee
@@ -55,7 +55,7 @@

     if not @currentPage
       # No current panel set, app just started, make start panel visible
-      @targetPage.style.display = "block"
+      @targetPage.style.display = "-webkit-box"
       @pageHistory = [window.location.hash]
       @currentPage = @targetPage
       return
@@ -92,7 +92,7 @@

   # performs slide animation transition
   slideTransition: () ->
-    @targetPage.style.display = "block"
+    @targetPage.style.display = "-webkit-box"

     if @back is true
       # must perform this initial tranform to get animation working in the next step
@@ -118,7 +118,7 @@

   # slides panel from bottom to top and top to bottom
   slideFromBottom: () ->
-     @targetPage.style.display = "block"
+     @targetPage.style.display = "-webkit-box"

      if @back is true
        # do reverse
@@ -128,7 +128,7 @@

      else
        #do forward transition
-       @targetPage.style.display = "block"
+       @targetPage.style.display = "-webkit-box"
        @translate(@targetPage, "Y", "100%","0ms")

        window.setTimeout =>
@@ -170,8 +170,8 @@

    # displays pages when transiton is false, back animations do not matter here.
    displayPage: =>
-    @targetPage.style.display = "block"
-    @currentPage.style.display = "block"
+    @targetPage.style.display = "-webkit-box"
+    @currentPage.style.display = "-webkit-box"
     @targetPage.style.left = "0%"
     @currentPage.style.left = "100%"
     @isTransitioning = false


Stop transition at the correct time. Prevents back being able to be pressed when page is changing causing history to be wrong.

--- js/flight.coffee
+++ js/flight.coffee
@@ -41,6 +41,8 @@

   # Goes to page with or without transtion
   goToPage: (options) =>
+
+    if @isTransitioning is true then return else @isTransitioning = true

     if options?
       if options.targetPanel?
@@ -79,8 +81,6 @@
     if speed
       @speed = speed

-    if @isTransitioning is true then return else @isTransitioning = true
-
     @targetPanel.style.display = "block"
     @currentPanel.style.display = "block"


Allow native command+backspace (delete from cursor to beginning of line)

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -73,11 +73,17 @@
     inputLog.groupEnd()

   mutationIsExpected: (mutationSummary) ->
-    return unless @inputSummary
-    return true if @inputSummary.keyName is "return"
-    unhandledAddition = mutationSummary.textAdded? and mutationSummary.textAdded isnt @inputSummary.textAdded
-    unhandledDeletion = mutationSummary.textDeleted? and not @inputSummary.didDelete
-    not (unhandledAddition or unhandledDeletion)
+    switch
+      when not @inputSummary
+        false
+      when @inputSummary.keyName is "backspace" and @inputSummary.keyModifier is "meta"
+        false
+      when @inputSummary.keyName is "return"
+        true
+      else
+        unhandledAddition = mutationSummary.textAdded? and mutationSummary.textAdded isnt @inputSummary.textAdded
+        unhandledDeletion = mutationSummary.textDeleted? and not @inputSummary.didDelete
+        not (unhandledAddition or unhandledDeletion)

   # File verification

@@ -97,7 +103,7 @@

       if keyName = @constructor.keyNames[event.keyCode]
         context = @keys
-        for modifier in ["ctrl", "alt", "shift"] when event["#{modifier}Key"]
+        for modifier in ["ctrl", "alt", "shift", "meta"] when event["#{modifier}Key"]
           modifier = "control" if modifier is "ctrl"
           context = @keys[modifier]
           if context[keyName]
@@ -278,10 +284,6 @@
         @responder?.insertString("\n", updatePosition: false)
         @requestRender()

-    alt:
-      backspace: (event) ->
-        @delegate?.inputControllerWillPerformTyping()
-
     shift:
       return: (event) ->
         @delegate?.inputControllerWillPerformTyping()
@@ -303,6 +305,14 @@
           event.preventDefault()
           @expandSelectionInDirection("forward")

+    alt:
+      backspace: (event) ->
+        @delegate?.inputControllerWillPerformTyping()
+
+    meta:
+      backspace: (event) ->
+        @delegate?.inputControllerWillPerformTyping()
+
   # Private

   deleteInDirection: (direction) ->


Add code comment for dependency

Add code comment for dependency

--- app/assets/javascripts/darkswarm/all.js.coffee
+++ app/assets/javascripts/darkswarm/all.js.coffee
@@ -9,6 +9,7 @@
 #= require angular-animate
 #= require angular-resource
 #= require lodash.underscore.js
+# bluebird.js is a dependency of angular-google-maps.js 2.0.0
 #= require bluebird.js
 #= require angular-scroll.min.js
 #= require angular-google-maps.min.js


Refactor quick_add_url with callbacks

Refactor quick_add_url with callbacks

This makes it reusable for image paste.

--- app/assets/javascripts/quick_add_url.js.coffee
+++ app/assets/javascripts/quick_add_url.js.coffee
@@ -1,38 +1,48 @@
+quick_add_url = (url, callbacks) ->
+  return if url == ''
+  callbacks.before_submit()
+
+  $.ajax '/src_images/',
+    type: 'post'
+    contentType: 'application/json'
+    dataType: 'json'
+    data: JSON.stringify(url: url)
+    success: (data) ->
+      callbacks.submit_success()
+      count = 0
+      timer = setInterval ->
+        callbacks.tick()
+        $.ajax "/src_images/#{data.id}",
+          type: 'head',
+          success: ->
+            clearInterval(timer)
+            callbacks.success(data.id)
+        count += 1
+        if count >= 10
+          clearInterval(timer)
+          callbacks.timed_out()
+      , 1000
+    error: callbacks.submit_error
+
 window.quick_add_url_init = (win) ->
   input_element = $('#quick-add-url')
-  status_element = $('#quick-add-url-status')
-
-  quick_add_url = ->
-    url = input_element.val()
-    return if url == ''
-    status_element.text('Submitting URL')
-
-    $.ajax '/src_images/',
-      type: 'post'
-      contentType: 'application/json'
-      dataType: 'json'
-      data: JSON.stringify(url: url)
-      success: (data) ->
-        status_element.text('Submitted URL')
-        count = 0
-        timer = setInterval ->
-          status_element.append('.')
-          $.ajax "/src_images/#{data.id}",
-            type: 'head',
-            success: ->
-              clearInterval(timer)
-              win.location.replace("/gend_images/new?src=#{data.id}")
-          count += 1
-          if count >= 10
-            clearInterval(timer)
-            status_element.text('Error loading URL')
-        , 1000
-      error: ->
-        status_element.text('Error submitting URL')

   input_element.keypress (e) ->
     if e.which == 13
-      quick_add_url()
+      status_element = $('#quick-add-url-status')
+      quick_add_url input_element.val(),
+        before_submit: ->
+          status_element.text('Submitting URL')
+        submit_success: ->
+          status_element.text('Submitted URL')
+        tick: ->
+          status_element.append('.')
+        success: (src_image_id) ->
+          win.location.replace("/gend_images/new?src=#{src_image_id}")
+        timed_out: ->
+          status_element.text('Error loading URL')
+        submit_error: ->
+          status_element.text('Error submitting URL')

 $(document).ready ->
   quick_add_url_init(window)


modify elements to do

modify elements to do

--- app/scripts/alchemy/core/modifyElements.coffee
+++ app/scripts/alchemy/core/modifyElements.coffee
@@ -1,3 +1,7 @@
+# to do: unique insertion of edges
+# to do: turn this into a generator so you can have variables such as
+# mousedown_node, mouseup_node, etc
+
 alchemy.modifyElements =
     init: () ->
         if alchemy.conf.showEditor is true


Fixed mistakes

Fixed mistakes

--- scripts/idobata-talk.coffee
+++ scripts/idobata-talk.coffee
@@ -122,7 +122,7 @@
     day  = date.getDay()
     hour = date.getHours()
     # deep night
-    if hour > 22 || hour < 3
+    if (22 < hour < 24) || (0 <= hour < 3)
       if robot.brain.data["event_night"] != day
         robot.brain.data["event_night"] = day
         msg.send msg.random [


Removes console logs

Removes console logs

--- app/views/modal/terrain_randomise_modal.coffee
+++ app/views/modal/terrain_randomise_modal.coffee
@@ -88,8 +88,6 @@
     presetType = target.attr 'data-preset-type'
     presetSize = target.attr 'data-preset-size'
     @randomiseThangs presetType, presetSize
-    # console.log target, target.attr 'data-preset-type'
-    # console.log target.attr 'data-preset-size'
     Backbone.Mediator.publish('randomise:terrain-generated',
       'thangs': @thangs
     )
@@ -101,7 +99,6 @@
     @randomiseFloor preset, presetSize
     @randomiseBorder preset, presetSize
     @randomiseDecorations preset, presetSize
-    # console.log _.range(0, presetSize.x, sizes.floorSize)

   randomiseFloor: (preset, presetSize) ->
     for i in _.range(0, presetSize.x, sizes.floorSize.x)
@@ -148,9 +145,7 @@
       }

   randomiseDecorations: (preset, presetSize)->
-    console.log preset.decorations
     for name, decoration of preset.decorations
-      console.log 'here', decoration
       for num in _.range(_.random(decoration.num[0], decoration.num[1]))
         center =
         {
@@ -167,7 +162,6 @@
           'x':center.x + decoration.width/2
           'y':center.y + decoration.height/2
         }
-        console.log center, min, max
         for cluster, range of decoration.clusters
           for i in _.range(_.random(range[0], range[1]))
             @thangs.push {


Form is currently unscoped

Form is currently unscoped

--- apps/artwork_2/components/inquiry/index.coffee
+++ apps/artwork_2/components/inquiry/index.coffee
@@ -6,5 +6,5 @@
   artwork = new Artwork INQUIRY.artwork

   view = new EmbeddedInquiryView
-    el: @$('.js-artwork-inquiry-form')
+    el: $('.js-artwork-inquiry-form')
     artwork: artwork


annulus selections

annulus selections

--- static/coffee/renderers/glyph/annulus.coffee
+++ static/coffee/renderers/glyph/annulus.coffee
@@ -12,8 +12,21 @@
 class AnnulusView extends GlyphView

   initialize: (options) ->
-    glyphspec = @mget('glyphspec')
-    @glyph_props = new glyph_properties(
+    ##duped in many classes
+    @glyph_props = @init_glyph(@mget('glyphspec'))
+    if @mget('selection_glyphspec')
+      spec = _.extend({}, @mget('glyphspec'), @mget('selection_glyphspec'))
+      @selection_glyphprops = @init_glyph(spec)
+    if @mget('nonselection_glyphspec')
+      spec = _.extend({}, @mget('glyphspec'), @mget('nonselection_glyphspec'))
+      @nonselection_glyphprops = @init_glyph(spec)
+    ##duped in many classes
+    @do_fill   = @glyph_props.fill_properties.do_fill
+    @do_stroke = @glyph_props.line_properties.do_stroke
+    super(options)
+
+  init_glyph : (glyphspec) ->
+    glyph_props = new glyph_properties(
       @,
       glyphspec,
       ['x', 'y', 'inner_radius', 'outer_radius'],
@@ -22,14 +35,16 @@
         new line_properties(@, glyphspec)
       ]
     )
-
-    @do_fill   = @glyph_props.fill_properties.do_fill
-    @do_stroke = @glyph_props.line_properties.do_stroke
-    super(options)
+    return glyph_props
+

   _set_data: (@data) ->
     @x = @glyph_props.v_select('x', data)
     @y = @glyph_props.v_select('y', data)
+    #duped
+    @selected_mask = new Array(data.length-1)
+    for i in [0..@selected_mask.length-1]
+      @selected_mask[i] = false

   _render: () ->
     [@sx, @sy] = @plot_view.map_to_screen(@x, @glyph_props.x.units, @y, @glyph_props.y.units)
@@ -39,11 +54,25 @@
     ctx = @plot_view.ctx

     ctx.save()
+   #duped
+    selected = @mget_obj('data_source').get('selected')
+    for idx in selected
+      @selected_mask[idx] = true
+
     if @glyph_props.fast_path
       @_fast_path(ctx)
     else
-      @_full_path(ctx)
-    ctx.restore()
+     if selected and selected.length and @nonselection_glyphprops
+        if @selection_glyphprops
+          props =  @selection_glyphprops
+        else
+          props = @glyph_props
+        @_full_path(ctx, props, 'selected')
+        @_full_path(ctx, @nonselection_glyphprops, 'unselected')
+      else
+        @_full_path(ctx)
+      ##duped in many classes
+     ctx.restore()

   _fast_path: (ctx) ->
     if @do_fill
@@ -68,22 +97,29 @@
         ctx.arc(@sx[i], @sy[i], @outer_radius[i], 0, 2*Math.PI*2, true)
         ctx.stroke()

-  _full_path: (ctx) ->
+  _full_path: (ctx, glyph_props, use_selection) ->
+    #duped
+    if not glyph_props
+      glyph_props = @glyph_props
     for i in [0..@sx.length-1]
       if isNaN(@sx[i] + @sy[i] + @inner_radius[i] + @outer_radius[i])
         continue
-
+      #duped
+      if use_selection == 'selected' and not @selected_mask[i]
+        continue
+      if use_selection == 'unselected' and @selected_mask[i]
+        continue
       ctx.beginPath()
       ctx.arc(@sx[i], @sy[i], @inner_radius[i], 0, 2*Math.PI*2, false)
       ctx.moveTo(@sx[i]+@outer_radius[i], @sy[i])
       ctx.arc(@sx[i], @sy[i], @outer_radius[i], 0, 2*Math.PI*2, true)

       if @do_fill
-        @glyph_props.fill_properties.set(ctx, @data[i])
+        glyph_props.fill_properties.set(ctx, @data[i])
         ctx.fill()

       if @do_stroke
-        @glyph_props.line_properties.set(ctx, @data[i])
+        glyph_props.line_properties.set(ctx, @data[i])
         ctx.stroke()

   draw_legend: (ctx, x1, x2, y1, y2) ->
@@ -124,6 +160,25 @@

     ctx.restore()

+  ##duped
+  select : (xscreenbounds, yscreenbounds) ->
+    xscreenbounds = [@plot_view.view_state.sx_to_device(xscreenbounds[0]),
+      @plot_view.view_state.sx_to_device(xscreenbounds[1])]
+    yscreenbounds = [@plot_view.view_state.sy_to_device(yscreenbounds[0]),
+      @plot_view.view_state.sy_to_device(yscreenbounds[1])]
+    xscreenbounds = [_.min(xscreenbounds), _.max(xscreenbounds)]
+    yscreenbounds = [_.min(yscreenbounds), _.max(yscreenbounds)]
+    selected = []
+    for i in [0..@sx.length-1]
+      if xscreenbounds
+        if @sx[i] < xscreenbounds[0] or @sx[i] > xscreenbounds[1]
+          continue
+      if yscreenbounds
+        if @sy[i] < yscreenbounds[0] or @sy[i] > yscreenbounds[1]
+          continue
+      selected.push(i)
+     return selected
+

 class Annulus extends Glyph
   default_view: AnnulusView


legends for segments

legends for segments

--- static/coffee/renderers/glyph/segment.coffee
+++ static/coffee/renderers/glyph/segment.coffee
@@ -70,6 +70,22 @@
         @glyph_props.line_properties.set(ctx, @data[i])
         ctx.stroke()

+  draw_legend: (ctx, x1, x2, y1, y2) ->
+    glyph_props = @glyph_props
+    line_props = glyph_props.line_properties
+    reference_point = @get_reference_point()
+    if reference_point?
+      glyph_settings = reference_point
+    else
+      glyph_settings = glyph_props
+    line_props.set(ctx, glyph_settings)
+    ctx.beginPath()
+    ctx.moveTo(x1, (y1 + y2) /2)
+    ctx.lineTo(x2, (y1 + y2) /2)
+    ctx.stroke()
+    ctx.beginPath()
+    ctx.restore()
+

 class Segment extends Glyph
   default_view: SegmentView


fix https://github.com/sagemathinc/smc/issues/1235 for directories in directories

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1126,10 +1126,13 @@
                 # Otherwise, we end up with copying foo to foo-1, resulting in a foo-1/foo/...
                 # See https://github.com/sagemathinc/smc/issues/1235
                 # Note that src has length 1 so the whole iteration business is overkill.
-                src = (path + (if @props.displayed_listing?.file_map[path]?.isdir then '/' else '') \
-                             for path in @props.checked_files.toArray())
+                f = (path) =>
+                    if @props.displayed_listing?.file_map[misc.path_split(path).tail]?.isdir
+                        return path + '/'
+                    else
+                        return path
                 @props.actions.copy_files
-                    src  : src
+                    src  : (f(path) for path in @props.checked_files.toArray())
                     dest : misc.path_to_file(rename_dir, destination)
         @props.actions.set_file_action()
         @props.actions.set_all_files_unchecked()


Stepped okSubmits date

Stepped okSubmits date

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -62,7 +62,7 @@
 export default dashboard = () ->
     queries =
         # remember that months start from 0
-        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2018, 7, 19)}},
+        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2019, 1, 1)}},
         ps: {ps: 1},
         wa: {solved: 0, ok: 0, ignored: 0, attempts: {$gt: 0}},
         ig: {ignored: 1},


Do not show cf-results of users without cf

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -40,6 +40,8 @@
     result.user = await User.findById(result.userId)
     if not result.user
         return undefined
+    if not result.user.cf
+        return undefined
     result.user = result.user.toObject()
     return result


Stepped okSubmits date

Stepped okSubmits date

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -56,7 +56,7 @@
 export default dashboard = () ->
     queries =
         # remember that months start from 0
-        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 9, 5)}},
+        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 9, 11)}},
         wa: {solved: 0, ok: 0, ignored: 0, attempts: {$gt: 0}},
         ig: {ignored: 1},
         ac: {solved: 1}


Add note on gcs config to config file

Add note on gcs config to config file

--- services/filestore/config/settings.defaults.coffee
+++ services/filestore/config/settings.defaults.coffee
@@ -31,6 +31,7 @@
 		# Choices are
 		# s3 - Amazon S3
 		# fs - local filesystem
+		# gcs - Google Cloud Storage
 		backend: process.env['BACKEND']

 		s3:
@@ -40,6 +41,9 @@
 				endpoint: process.env['AWS_S3_ENDPOINT']
 				pathStyle: process.env['AWS_S3_PATH_STYLE']
 				partSize: process.env['AWS_S3_PARTSIZE'] or (100 * 1024 * 1024)
+
+		# GCS should be configured by the service account on the kubernetes pod. See GOOGLE_APPLICATION_CREDENTIALS,
+		# which will be picked up automatically.

 		stores:
 			user_files: process.env['USER_FILES_BUCKET_NAME']


cost params

cost params

--- js/render.coffee
+++ js/render.coffee
@@ -42,7 +42,7 @@
      if BREAK_COST[node.type]?
       @breakCostMap[node.id] = cost + BREAK_COST[node.type]
      else if node.type is 'section'
-      @breakCostMap[node.id] = cost + 100 * (node.level - 2)
+      @breakCostMap[node.id] = cost + 100 * (node.level - 3)
      else
       throw new Error 'Unknown type'


Compute providers: minor fix

Compute providers: minor fix

--- workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
+++ workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
@@ -1,4 +1,5 @@
-async = require 'async'
+async       = require 'async'
+KodingError = require '../../../error'

 log = ->
   console.log '[handlers:memberremoved]', arguments...
@@ -113,7 +114,7 @@
         if err or not user
           errorMessage = 'Failed to fetch member'
           log "#{errorMessage}:", err
-          next err ? new Error errorMessage
+          next err ? new KodingError errorMessage
         else
           memberJUser = user
           next()


add comment about using better invalidation strategy

--- src/packages/bookmarks/lib/bookmarks-view.coffee
+++ src/packages/bookmarks/lib/bookmarks-view.coffee
@@ -86,6 +86,8 @@

   createBookmarkMarker: (bufferRow) ->
     range = [[bufferRow, 0], [bufferRow, 0]]
+
+    # TODO: use the 'surround' strategy when collaboration is merged in
     @displayBuffer().markBufferRange(range, @bookmarkMarkerAttributes(invalidationStrategy: 'never'))

   findBookmarkMarkers: (attributes={}) ->


check user

check user

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -17,7 +17,9 @@
   autoSignIn: (path) ->
     console.log 'autoSignIn'
     if user = sessionStorage.getItem('travis.user')
-      @setData(user: JSON.parse(user))
+      data = JSON.parse(user)
+      data = { user: data } unless data.user?
+      @setData(data)
     else if localStorage.getItem('travis.auto_signin')
       console.log 'travis.auto_signin', localStorage.getItem('travis.auto_signin')
       @signIn()


More debug output

More debug output

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -55,7 +55,7 @@
     await @upsert()

 submitsSchema.methods.equivalent = (other) ->
-    if @user == "394891"
+    if @user == "394891" and @_id == "18336986p2951" and other._id == "2088r683956p2951"
         logger.log "Compare submits ", @_id, other._id, @comments.length > 0, @outcome == "AC" or @outcome == "IG" or @outcome == "DQ", @force, @user == other.user, @problem == other.problem, outcomeType(@outcome) == outcomeType(other.outcome), @source.replace("\r", "") == other.source.replace("\r", ""), Math.abs(@time - other.time)
         logger.log "s1=`#{Buffer.from(@source)}` `#{Buffer.from(@source.replace("\r", ""))}`"
         logger.log "s2=`#{Buffer.from(other.source)}`, `#{Buffer.from(other.source.replace("\r", ""))}`"
@@ -63,9 +63,9 @@
         s2 = other.source.replace("\r", "")
         logger.log "s1,s2.length=", s1.length, s2.length, @_id, other._id
         for i in [0..s1.length - 1]
+            logger.log i, s1.charCodeAt(i), s2.charCodeAt(i), @_id, other._id
             if s1.charAt(i) != s2.charAt(i)
-                logger.log "!!!", i, @_id, other._id
-                break
+                logger.log "!!!", i, s1.charCodeAt(i), s2.charCodeAt(i), @_id, other._id
     if @comments.length > 0
         return false
     if @outcome == "AC" or @outcome == "IG" or @outcome == "DQ"


another attemp

another attemp

--- src/pack.iced
+++ src/pack.iced
@@ -28,7 +28,7 @@
     pp = require 'purepack'
   if pp?
     mp = null
-    set_opt byte_array, true
+    set_opt "byte_arrays", true
   else
     throw new Error "Cannot use_byte_arrays without purepack!"


sidebar: fix unread count is not set to 0 problem after glance

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -298,6 +298,10 @@
     item = app.getView().sidebar.selectedItem

     return  unless item?.count
+
+    # do not wait for response to set it as 0
+    item.setUnreadCount 0
+
     # no need to send updatelastSeenTime or glance when checking publicfeeds
     return  if name in ['public', 'announcement']


better all on maps.

better all on maps.

--- client/coffee/controllers/geojsonmapCtrl.coffee
+++ client/coffee/controllers/geojsonmapCtrl.coffee
@@ -1,8 +1,13 @@
 root = this;
+
+
+
 mapCenter =
     lat: 46.1227
     lng: -72.6169
     zoom: 7
+
+

 getSingleDataUrl = (routeParams) ->
     url = "/api/#{ routeParams.server }/#{ routeParams.database }/#{ routeParams.collection }"
@@ -21,14 +26,19 @@

 transformListOfGeoJsonToGeometryCollection = (resData, routeParams) ->
     ret =
-        type: "GeometryCollection"
-        geometries: []
+        type: "FeatureCollection"
+        features: []
     for doc in resData.document
         geojson = doc[routeParams.key]
         if not geojson.properties
             geojson.properties = {}
         geojson.properties["_vulture_url_link"] = getDocumentUrl(routeParams, doc._id)
-        ret.geometries.push(geojson)
+        feature =
+            type: "Feature"
+            geometry: geojson,
+            properties:
+                "_vulture_url_link": getDocumentUrl(routeParams, doc._id)
+        ret.features.push(geojson)
     return ret


fix mistake in preselected conditions for a6.1

--- app/assets/javascripts/frontend/custom_questions/options_with_preselected_conditions_question.js.coffee
+++ app/assets/javascripts/frontend/custom_questions/options_with_preselected_conditions_question.js.coffee
@@ -56,7 +56,7 @@
       if award_category == "international_trade" && applied && won
         if award_year > application_threshold_year
           trade_award_and_above_application_threshold_year = true
-        else if award_year >= threshold_year
+        else if award_year > threshold_year
           trade_award_and_above_threshold_year = true

     # Show C1 question based on this condition


adds fair metadata overrides

adds fair metadata overrides

--- models/mixins/meta_overrides.coffee
+++ models/mixins/meta_overrides.coffee
@@ -22,6 +22,102 @@

 metaOverrides = (model) ->

+  'arco-madrid-2016':
+    title: 'ARCOmadrid 2016 | Artsy'
+    description: "Explore a special section commemorating ARCOmadrid’s 35th edition, featuring galleries like Victoria Miro, Marian Goodman Gallery, and Annet Gelink Gallery"
+
+  'the-armory-show-2016':
+    title: 'The Armory Show 2016 | Artsy'
+    description: "From Galeria Nara Roesler to Tina Kim Gallery, browse works from the 170+ international galleries gathered in New York City on Piers 92 & 94"
+
+  'moving-image-new-york-2016':
+    title: 'Moving Image: New York 2016 | Artsy'
+    description: "Discover moving-image works by artists including boredomresearch, Lorna Mills, and Olivia McGilchrist"
+
+  'springbreak-2016':
+    title: 'SPRING/BREAK 2016 | Artsy'
+    description: "See how 70+ curators—including Arielle de Saint Phalle + Taylor Roy, Chris Bors, and Marly Hammer—tackle issues of appropriation and image reproduction through their interpretations of this year’s theme, “⌘COPY⌘PASTE”"
+
+  'pulse-new-york-2016':
+    title: 'PULSE New York 2016 | Artsy'
+    description: "As the art world gathers in New York, explore the work of emerging and established artists—Chitra Ganesh, Tony Feher, and William Powhida among them—on view in Chelsea at this international fair"
+
+  'art-basel-in-hong-kong-2016':
+    title: 'Art Basel in Hong Kong 2016 | Artsy'
+    description: "Find out what de Sarthe Gallery, Annely Juda Fine Art, and Simon Lee Gallery are bringing to the fair’s fourth edition"
+
+  'art-central-2016':
+    title: 'Art Central Hong Kong 2016 | Artsy'
+    description: "Preview work from galleries hailing from Asia and beyond—Whitestone Gallery, amanasalto, and Sundaram Tagore Gallery among them"
+
+  'sp-arte-2016':
+    title: 'SP-Arte 2016 | Artsy'
+    description: "Your first look inside the São Paulo fair that will bring together 140+ galleries from across the world for its 12th edition"
+
+  'miart-2016':
+    title: 'miart 2016 | Artsy'
+    description: "Explore the wealth of galleries—including Galerie Lelong, Steve Turner, and Lia Rumma—together in Milan for the 21st edition of this international modern and contemporary fair "
+
+  'dallas-art-fair-2016':
+    title: 'Dallas Art Fair 2016 | Artsy'
+    description: "Next week, the Lone Star state will play host to 90+ local and international galleries—bitforms gallery, Galerie Perrotin, and Conduit Gallery among them—for the eighth edition of this Dallas fair"
+
+  'art-cologne-2016':
+    title: 'Art Cologne 2016 | Artsy'
+    description: "An early look at the 100+ galleries exhibiting at the 50th edition of the world’s first modern and contemporary fair"
+
+  'aipad-2016':
+    title: 'AIPAD 2016 | Artsy'
+    description: "Explore the 36th edition of this New York fair, including work by the world’s leading photographers—Cig Harvey, Hellen van Meene, and Garry Winogrand among them"
+
+  'art-brussels-2016':
+    title: 'Art Brussels 2016 | Artsy'
+    description: "Underrepresented artists of the 20th century—Yuko Nasaka and Bruno Munari among them—get a second look in this Belgian fair’s new section, “REDISCOVERY”"
+
+  'london-original-print-fair-2016':
+    title: 'London Original Print Fair 2016 | Artsy'
+    description: "Here’s your sneak peek at the prints—from works by Old Masters to emerging artists—en route to London’s Royal Academy of Arts"
+
+  'collective-design-2016':
+    title: 'Collective Design Fair 2016 | Artsy'
+    description: "An early look at works by the world's leading designers, from the Campana Brothers to Lindsey Adelman"
+
+  'frieze-new-york-2016':
+    title: 'Frieze New York 2016 | Artsy'
+    description: "From Tatiana Trouvé to Michael E. Smith, preview works from the art world’s rising stars soon to be shown on Randall's Island"
+
+  'art-newyork-2016':
+    title: 'Art Miami New York 2016 | Artsy'
+    description: "Explore works by 250+ artists—including Carlos Cruz-Diez, Alexander Calder, and Yayoi Kusama—now on view at Pier 94"
+
+  '1-54-new-york-2016':
+    title: '1:54 Contemporary African Art Fair New York 2016 | Artsy'
+    description: "An offshoot of 1:54 London, this New York edition showcases work by 60+ artists from Africa and the African diaspora"
+
+  'art16':
+    title: 'Art16 | Artsy'
+    description: "In advance of this London fair, discover 1,100+ works from leading contemporary artists—Miya Ando and El Anatsui among them"
+
+  'photo-london-2016':
+    title: 'Photo London 2016 | Artsy'
+    description: "Get your first look at works by the world’s leading photographers—including Annie Leibovitz, Martin Schoeller, and Vivian Maier—soon to take up residence in London’s Somerset House"
+
+  'arteba-2016':
+    title: 'arteBA 2016 | Artsy'
+    description: "See what Galeria Marilia Razuk, Blau Projects, and Cosmocosa are bringing to Buenos Aires as 85 galleries gather for this Argentinian fair"
+
+  'loop-barcelona-2016':
+    title: 'LOOP Barcelona | 2016'
+    description: "Preview video work by 45+ galleries showing at this intimate fair in a Barcelona hotel"
+
+  'art-basel-2016':
+    title: 'Art Basel 2016 | Artsy'
+    description: "Explore work from the art world's much-anticipated summer event, bringing leading international galleries to Basel"
+
+  'design-miami-basel-2016':
+    title: 'Design Miami/ Basel 2016 | Artsy'
+    description: "See what's on view at Art Basel's sister fair, highlighting exquisite design pieces from around the world"
+
   'zsona-maco-2016':
     title: 'ZsONA MACO 2016 | Artsy'
     description: 'Browse works by leading contemporary artists—Rachel Howard, Candida Höfer, and Jose Dávila included—shown in Mexico’s Distrito Federal'


fix typo

fix typo

--- atmospheric/002-accumulated-heat/viewer.coffee
+++ atmospheric/002-accumulated-heat/viewer.coffee
@@ -75,7 +75,7 @@
                 else
                     point = idx + 1
                 points.push(point)
-            idx ++
+            idx++
         points
     contour = (positioning, heatdata) ->
         for idx in [0...32]


Fix Firefox Profile Edit issues

Fix Firefox Profile Edit issues

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -16,7 +16,7 @@
       @resetInputValue?()
       @unsetClass 'active'
     else if @$().hasClass(classToCheck) and e.which is 13
-      @submit event
+      @submit e

   mouseDown:(event)->
     @showForm()


Move to start of week and use that range

--- client/skr/screens/time-tracking/Entries.coffee
+++ client/skr/screens/time-tracking/Entries.coffee
@@ -118,10 +118,8 @@
         , _.bigDecimal('0') )

     totalsForWeek: (week) ->
-        days = _.moment.range(
-            @range.start.clone().add(week - 1, 'week'),
-            @range.start.clone().add(week, 'week')
-        )
+        start = @range.start.clone().add(week - 1, 'week').startOf('week')
+        days = _.moment.range( start, start.clone().endOf('week') )
         entries = @entries.filter (entry) -> days.overlaps( entry.range )
         byProject = _.groupBy entries, (entry) -> entry.customer_project_id
         _.mapValues byProject, (entries, projectId) ->


Netsniff.coffee example should exit with an error when fails to load the adress

Issue #11333 https://github.com/ariya/phantomjs/issues/11333

--- examples/netsniff.coffee
+++ examples/netsniff.coffee
@@ -103,6 +103,7 @@
   page.open page.address, (status) ->
     if status isnt 'success'
       console.log 'FAIL to load the address'
+      phantom.exit(1)
     else
       page.endTime = new Date()
       page.title = page.evaluate ->
@@ -110,4 +111,4 @@

       har = createHAR page.address, page.title, page.startTime, page.resources
       console.log JSON.stringify har, undefined, 4
-    phantom.exit()
+      phantom.exit()


Removing reference to ticking.js, which was removed in the ticking refactor

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -34,7 +34,6 @@
   Bokeh.Random = require("common/random")
   Bokeh.safebind = require("common/safebind")
   Bokeh.SVGColors = require("common/svg_colors")
-  Bokeh.ticking = require("common/ticking")
   Bokeh.ViewState = require("common/view_state")

   # mappers


fixed url generating

fixed url generating

--- test/swagger-property-normalizer-spec.coffee
+++ test/swagger-property-normalizer-spec.coffee
@@ -36,6 +36,8 @@
               "http"
               "https"
             ]
+
+          @sut = new SwaggerPropertyNormalizer @wingedKoboldSwagger
           @result = @sut.getBaseUrl @wingedKoboldSwagger

         it 'should return the https-based url', ->


Remove status icon

Remove status icon

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -62,8 +62,6 @@
               @span class: 'icon icon-playback-pause'
               @span class: 'disable-text', 'Disable'

-        @span outlet: 'status', class: 'package-status icon'
-
   initialize: (@pack, @packageManager) ->
     @type = if @pack.theme then 'theme' else 'package'
     @name = @pack.name
@@ -120,10 +118,7 @@
   handlePackageEvents: ->
     @subscribeToPackageEvent 'package-installed package-install-failed theme-installed theme-install-failed', (pack, error) =>
       @installButton.prop('disabled', false)
-      if error?
-        @setStatusIcon('alert')
-      else
-        @setStatusIcon('check')
+      unless error?
         @settingsButton.show()
         @installButton.hide()
         @uninstallButton.show()
@@ -132,26 +127,20 @@
       @installButton.prop('disabled', true)
       @installButton.show()
       @uninstallButton.hide()
-      @setStatusIcon('cloud-download')

     @subscribeToPackageEvent 'package-uninstalling', (pack) =>
       @installButton.prop('disabled', true)
-      @setStatusIcon()

     @subscribeToPackageEvent 'package-uninstalled package-uninstall-failed theme-uninstalled theme-uninstall-failed', (pack, error) =>
       @installButton.prop('disabled', false)
-      if error?
-        @setStatusIcon('alert')
-      else
+      unless error?
         @installButton.show()
         @uninstallButton.hide()
         @settingsButton.hide()
-        @setStatusIcon()

     if @isInstalled()
       @installButton.hide()
       @uninstallButton.show()
-      @setStatusIcon('check')
     else
       @settingsButton.hide()

@@ -178,15 +167,3 @@
     @packageManager.uninstall @pack, (error) =>
       if error?
         console.error("Uninstalling #{@type} #{@pack.name} failed", error.stack ? error, error.stderr)
-
-  setStatusIcon: (iconName) ->
-    @status.removeClass('icon-check icon-alert icon-cloud-download')
-    @status.addClass("icon-#{iconName}") if iconName
-    @status.destroyTooltip()
-    switch iconName
-      when 'check'
-        @status.setTooltip(_.capitalize("#{@type} installed"))
-      when 'alert'
-        @status.setTooltip(_.capitalize("#{@type} failed to install"))
-      when 'cloud-download'
-        @status.setTooltip(_.capitalize("#{@type} installing"))


ui 修改

ui 修改

--- lib/build/build-state-view.coffee
+++ lib/build/build-state-view.coffee
@@ -161,6 +161,11 @@
     selectConfirm=confirm("请确认是否中止编译？");
     if selectConfirm
       if @task and @task.state != 'complete'
+        @loading.hide()
+        @find('.glyphicon-remove').removeClass('hidden')
+        @find('.task-state').text "failed"
+        @cancelbutton.disable()
+        @refreshbutton.disable()
         puzzleClient.deleteTask @task.id
         .then ->
           console.log "task #{@task.id} deleted."


commit

commit

--- lib/build/build-state-view.coffee
+++ lib/build/build-state-view.coffee
@@ -70,7 +70,7 @@
     if atom.config.get('atom-chameleon.puzzleServerAddress') is "http://bsl.foreveross.com/puzzle"
       ioHttp = "http://115.28.1.109:8000/socketio"
     else
-      ioHttp = atom.config.get('atom-chameleon.puzzleServerAddress')
+      ioHttp = atom.config.get('atom-chameleon.puzzleServerAddress').replace("puzzle","socketio")

     @socket = io ioHttp,
       reconnection: true


added on_success & on_failure for channels

--- lib/assets/javascripts/websocket_rails/channel.js.coffee
+++ lib/assets/javascripts/websocket_rails/channel.js.coffee
@@ -10,7 +10,7 @@
 class WebSocketRails.Channel

   constructor: (@name,@dispatcher) ->
-    event = new WebSocketRails.Event( ['websocket_rails.subscribe', {data: {channel: @name}}] )
+    event = new WebSocketRails.Event( ['websocket_rails.subscribe', {data: {channel: @name}}], @on_success, @on_failure)
     @dispatcher.trigger_event event
     @callbacks = {}


activity: put load more marker if only message was not loaded before

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -127,11 +127,14 @@
   loadMessage(messageId).then ({ message }) ->
     channelId = message.initialChannelId
     loadMessages(channelId, { from: message.createdAt }).then ({ messages }) ->
+      messagesBefore = reactor.evaluate ['MessagesStore']
       loadMessages(channelId, { from: message.createdAt, sortOrder: 'ASC' }).then ({ messages }) ->
         [..., last] = messages
         return { message }  unless last

-        putLoaderMarker channelId, last.id, { position: 'after', autoload: no }
+        # put a loader marker only if this message was not here before.
+        unless messagesBefore.has last.id
+          putLoaderMarker channelId, last.id, { position: 'after', autoload: no }

         return { message }


Client: Activity: Feed: Added setChannelMessagesSearchQuery method.

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -585,6 +585,12 @@
   { DISABLE_EDITED_MESSAGE_EMBED_PAYLOAD } = actionTypes
   dispatch DISABLE_EDITED_MESSAGE_EMBED_PAYLOAD, { messageId }

+
+setChannelMessagesSearchQuery = (query) ->
+
+  { SET_CHANNEL_MESSAGES_SEARCH_QUERY } = actionTypes
+
+  dispatch SET_CHANNEL_MESSAGES_SEARCH_QUERY, { query }

 module.exports = {
   loadMessage
@@ -606,4 +612,5 @@
   removeLoaderMarker
   editEmbedPayloadByUrl
   disableEditedEmbedPayload
+  setChannelMessagesSearchQuery
 }


shortcuts/ctrl: refactor

shortcuts/ctrl: refactor

--- client/app/lib/shortcuts/list-controller.coffee
+++ client/app/lib/shortcuts/list-controller.coffee
@@ -1,18 +1,17 @@
-kd = require 'kd'
+kd                = require 'kd'
 ShortcutsListItem = require './list-item'
+xtend             = require 'xtend'

 module.exports =

 class ShortcutsListController extends kd.ListViewController

-  constructor: (options={}, collection) ->
+  constructor: (options={}, data) ->

-    options.useCustomScrollView = no
-    options.viewOptions =
-      itemClass: ShortcutsListItem
+    options = xtend options,
+      selection: true
+      view: new kd.ListView
+        cssClass: 'shortcuts-list'
+        itemClass: ShortcutsListItem

-    super options, collection
-
-    collection.each (model) =>
-      @addItem
-        name: model.description
+    super options, data


rudimentary for loop to iterate over sections

--- client/apps/edit/components/layout/index.coffee
+++ client/apps/edit/components/layout/index.coffee
@@ -157,9 +157,15 @@
   onKeyup: =>
     #Issues to deal with:
     #I want it to check constantly OR every time I press a button, right now it checks the first time I type
-    #I want it to check only text sections
-    #I want it to check all text sections
-    $("#edit-seo__content-field").val($(this.article.get("sections")[0].body).text())
+    #I want it to check only text sections--not quite true; I want it to note that there are images present
+    #I want it to check all text sections\
+    # debugger
+    # $("#edit-seo__content-field").val($(this.article.get("sections")[0].body).text())
+    fullText = []
+    # fulltext.push(lead-paragraph) -- pseudo code
+    fullText.push($(section.body).text()) for section in $(this.article.get("sections")) when section.type is "text"
+    fullText = fullText.join(' ')
+    $("#edit-seo__content-field").val(fullText).text()
     $("#snippet-editor-title").val(this.article.get("title"))
     if @article.get('published')
       @changedSection = true


Use notifications in the "navigationBar" controller

--- testApp/app/controllers/navigationbar.coffee
+++ testApp/app/controllers/navigationbar.coffee
@@ -10,7 +10,7 @@
     steroids.view.navigationBar.hide {
       animated: true
     }, {
-      onSuccess: -> alert "hided with animation"
+      onSuccess: -> notification "hidden with animation"
       onFailure: -> alert "failed to hide"
     }

@@ -18,67 +18,67 @@
     steroids.view.navigationBar.show {
       animated: true
     }, {
-      onSuccess: -> alert "showed with animation"
+      onSuccess: -> notification "shown with animation"
       onFailure: -> alert "failed to show"
     }

   @testHide: ->
     steroids.view.navigationBar.hide {},
-      onSuccess: -> alert "hided"
+      onSuccess: -> notification "hidden"
       onFailure: -> alert "failed to hide"

   @testShow: ->
     steroids.view.navigationBar.show {},
-      onSuccess: -> alert "showed"
+      onSuccess: -> notification "showed"
       onFailure: -> alert "failed to show"

   @testShowWithTitle: ->
     steroids.view.navigationBar.show {
       title: "Any title"
     },
-      onSuccess: -> alert "showed with title"
+      onSuccess: -> notification "shown with title"
       onFailure: -> alert "failed to show with title"

   @testShowWithTitleImagePath: ->
     steroids.view.navigationBar.show {
       titleImagePath: "/icons/pill@2x.png"
     },
-    onSuccess: -> alert "showed with titleImagePath"
+    onSuccess: -> notification "shown with titleImagePath"
     onFailure: -> alert "failed to show with titleImagePath"

   @testSetButtonsWithOneRightButton: ->
     button = new steroids.buttons.NavigationBarButton
     button.title = "TEST"
-    button.onTap = => alert "RIGHT BUTTON TAPPED"
+    button.onTap = => notification "RIGHT BUTTON TAPPED"

     steroids.view.navigationBar.setButtons {
       right: [button]
     },
-      onSuccess: => alert "buttons set"
+      onSuccess: => notification "buttons set"
       onFailure: => alert "failed to set buttons"

   @testSetButtonsWithManyButtons: (options={override:false})->
     button1 = new steroids.buttons.NavigationBarButton
     button1.title = "EKA"
-    button1.onTap = => alert "EKA BUTTON TAPPED"
+    button1.onTap = => notification "EKA BUTTON TAPPED"

     button2 = new steroids.buttons.NavigationBarButton
     button2.title = "TOKA"
-    button2.onTap = => alert "TOKA BUTTON TAPPED"
+    button2.onTap = => notification "TOKA BUTTON TAPPED"

     button3 = new steroids.buttons.NavigationBarButton
     button3.title = "NoCB"

     button4 = new steroids.buttons.NavigationBarButton
     button4.imagePath = "/icons/pill@2x.png"
-    button4.onTap = => alert "ICON BUTTON TAPPED"
+    button4.onTap = => notification "ICON BUTTON TAPPED"

     steroids.view.navigationBar.setButtons {
       overrideBackButton: options.override
       left: [button1, button2]
       right: [button3, button4]
     },
-      onSuccess: => alert "many buttons set"
+      onSuccess: => notification "many buttons set"
       onFailure: => alert "failed to set many buttons"

   @testSetButtonsWithManyButtonsWithoutBack: ->
@@ -86,31 +86,31 @@

   @testSetButtonsWithoutButtons: ->
     steroids.view.navigationBar.setButtons {},
-      onSuccess: => alert "all buttons removed"
+      onSuccess: => notification "all buttons removed"
       onFailure: => alert "failed to remove all buttons"

   @testUpdateTitle: ->
     steroids.view.navigationBar.update {
       title: "New title"
     },
-      onSuccess: -> alert "title updated"
+      onSuccess: -> notification "title updated"
       onFailure: -> alert "failed to update title"

   @testUpdateTitleImage: ->
     steroids.view.navigationBar.update {
       titleImagePath: "/icons/pill@2x.png"
     },
-      onSuccess: -> alert "title image updated"
+      onSuccess: -> notification "title image updated"
       onFailure: -> alert "failed to update image title"

   @testUpdateWithButtons: ->
     button1 = new steroids.buttons.NavigationBarButton
     button1.title = "RIGHT"
-    button1.onTap = => alert "RIGHT BUTTON TAPPED"
+    button1.onTap = => notification "RIGHT BUTTON TAPPED"

     button2 = new steroids.buttons.NavigationBarButton
     button2.imagePath = "/icons/pill@2x.png"
-    button2.onTap = => alert "LEFT BUTTON TAPPED"
+    button2.onTap = => notification "LEFT BUTTON TAPPED"

     steroids.view.navigationBar.update {
       buttons: {
@@ -118,7 +118,7 @@
         left: [button2]
       }
     },
-      onSuccess: -> alert "nav bar buttons updated"
+      onSuccess: -> notification "nav bar buttons updated"
       onFailure: -> alert "failed to update nav bar buttons"

   @testUpdateWithNoButtons: ->
@@ -128,13 +128,14 @@
         left: []
       }
     },
-      onSuccess: -> alert "nav bar buttons updated"
+      onSuccess: -> notification "nav bar buttons updated"
       onFailure: -> alert "failed to update nav bar buttons"

   @testOverrideBackbuttonTrue: ->
     steroids.view.navigationBar.update {
       overrideBackButton: true
-    }
+    },
+      onSuccess: -> notification "nav bar buttons updated"

   @testOverrideBackbuttonFalse: ->
     steroids.view.navigationBar.update {
@@ -165,11 +166,11 @@
   @testUpdateButtonsWithoutBackButton: ->
     button1 = new steroids.buttons.NavigationBarButton
     button1.title = "NEW RIGHT"
-    button1.onTap = => alert "NEW RIGHT BUTTON TAPPED"
+    button1.onTap = => notification "NEW RIGHT BUTTON TAPPED"

     button2 = new steroids.buttons.NavigationBarButton
     button2.imagePath = "/icons/pill@2x.png"
-    button2.onTap = => alert "LEFT BUTTON TAPPED"
+    button2.onTap = => notification "LEFT BUTTON TAPPED"

     steroids.view.navigationBar.update {
       overrideBackButton: true
@@ -178,19 +179,19 @@
         left: [button2]
       }
     },
-      onSuccess: -> alert "nav bar buttons updated"
+      onSuccess: -> notification "nav bar buttons updated"
       onFailure: -> alert "failed to update nav bar buttons"

   @testButtonWithImageAsOriginal: ->
     btoImgOriginal = new steroids.buttons.NavigationBarButton
     btoImgOriginal.imagePath = "/icons/apple_colorfull.png"
     btoImgOriginal.imageAsOriginal = "true"
-    btoImgOriginal.onTap = => alert "BUTTON WITH ORIGINAL IMAGE TAPPED"
+    btoImgOriginal.onTap = => notification "BUTTON WITH ORIGINAL IMAGE TAPPED"

     btoTintedImg = new steroids.buttons.NavigationBarButton
     btoTintedImg.imagePath = "/icons/apple_colorfull.png"
     # btoTintedImg.imageAsOriginal = "true"
-    btoTintedImg.onTap = => alert "BUTTON WITH TINTED IMAGE TAPPED"
+    btoTintedImg.onTap = => notification "BUTTON WITH TINTED IMAGE TAPPED"

     steroids.view.navigationBar.update {
       buttons: {
@@ -198,7 +199,7 @@
         left: [btoTintedImg]
       }
     },
-      onSuccess: -> alert "nav bar buttons updated"
+      onSuccess: -> notification "nav bar buttons updated"
       onFailure: -> alert "failed to update nav bar buttons"

   @testSetAppearanceToRainbow: ->
@@ -209,6 +210,6 @@
       portraitBackgroundImage: steroids.app.path + '/images/navbar-bg@2x.png'
       landscapeBackgroundImage: steroids.app.path + '/images/navbar-bg@2x.png'
     }, {
-      onSuccess: -> alert "taste the rainbow"
+      onSuccess: -> notification "taste the rainbow"
       onFailure: -> alert "failed set nav bar appearance"
     }


onPractice is only used by panel, not sections

--- src/components/learning-guide/weaker-sections.cjsx
+++ src/components/learning-guide/weaker-sections.cjsx
@@ -12,13 +12,9 @@
   propTypes:
     courseId:     React.PropTypes.string.isRequired
     sections:     React.PropTypes.array.isRequired
-    onPractice:   React.PropTypes.func
     sectionCount: React.PropTypes.number
     weakerEmptyMessage:  React.PropTypes.string.isRequired
     minimumSectionCount: React.PropTypes.number
-
-  getDefaultProps: ->
-    sectionCount: 4, minimumSectionCount: 2

   renderLackingData: ->

{@props.weakerEmptyMessage}


Simplify file-list code

Simplify file-list code

--- lib/models/files/file-list.coffee
+++ lib/models/files/file-list.coffee
@@ -2,8 +2,8 @@

 git           = require '../../git'
 List          = require '../list'
+StagedFile    = require './staged-file'
 UnstagedFile  = require './unstaged-file'
-StagedFile    = require './staged-file'
 UntrackedFile = require './untracked-file'
 ErrorView     = require '../../views/error-view'

@@ -17,16 +17,15 @@
   # Internal: Populate the file list.
   #
   # status - The status to populate the file list with as {Object}.
-  populate: (status, silent) ->
-    @populateList(status.untracked, @untracked(), UntrackedFile)
-    @populateList(status.unstaged, @unstaged(), UnstagedFile)
-    @populateList(status.staged, @staged(), StagedFile)
+  populate: (status, silent) =>
+    @reset()
+
+    @populateList(status.staged, StagedFile)
+    @populateList(status.unstaged, UnstagedFile)
+    @populateList(status.untracked, UntrackedFile)

     @select(@selectedIndex)
     @trigger('repaint') unless silent
-
-  comparator: (file) ->
-    file.sortValue

   # Public: Return the staged files.
   staged: ->
@@ -40,52 +39,11 @@
   untracked: ->
     @filter (file) -> file.isUntracked()

-  # Internal: Which of the paths are new?
-  #
-  # paths - The paths to test as {Array}.
-  # files - The known file paths as {Array}.
-  #
-  # Returns the new paths as {Array}.
-  newPaths: (paths, files) ->
-    _.filter paths, (path) ->
-      not _.any files, (file) ->
-        file.path() == path.path
-
-  # Internal: Which of the known files are missing?
-  #
-  # paths - The paths to test as {Array}.
-  # files - The known file paths as {Array}.
-  #
-  # Returns the missing file paths as {Array}.
-  missingFiles: (paths, files) ->
-    paths = _.map(paths, 'path')
-    _.filter files, (file) ->
-      not _.contains(paths, file.path())
-
-  # Public: Which of the known files are still there?
-  #
-  # paths - The paths to test as {Array}.
-  # files - The known file paths as {Array}.
-  #
-  # Returns the paths of the files that are still there as {Array}.
-  stillThereFiles: (paths, files) ->
-    paths = _.map(paths, 'path')
-    _.filter files, (file) ->
-      _.contains(paths, file.path())
-
   # Internal: Populate the list.
   #
-  # paths - The paths to populate with as {Array}.
-  # files - The already tracked files as {Array}.
+  # files - The files to populate the list with as {Array}.
   # Klass - The Klass these paths are to be tracked as as {Object}.
-  populateList: (paths, files, Klass) ->
-    _.each @stillThereFiles(paths, files), (file) ->
-      file.loadDiff()
-
-    _.each @newPaths(paths, files), (path) =>
-      @add new Klass path
-
-    _.each @missingFiles(paths, files), (file) =>
-      @remove file
+  populateList: (files, Klass) =>
+    _.each files, (file) => @add new Klass(file)

 module.exports = FileList


添加获取包路径方法

添加获取包路径方法

--- lib/utils/text-description.coffee
+++ lib/utils/text-description.coffee
@@ -32,6 +32,8 @@
   registerUrl : 'http://www.baidu.com'

   chameleonHome: atom.packages.getLoadedPackage('chameleon').path
+  getChameleonHomePath: ->
+    @chameleonHome
   getFrameworkPath: ->
     Path.join @chameleonHome,'src','frameworks'
   newProjectDefaultPath: atom.config.get('core').projectHome
@@ -57,7 +59,7 @@
   buildProjectMainTitle: "构建项目"
   uploadProjectTitle: "上传应用"

-  newsTemplate:
+  newsTemplate:
     name: '新闻'
     type: 'news'
     pic: 'http://7xifa4.com1.z0.glb.clouddn.com/a.png'


fix asset reference

fix asset reference

--- components/analytics/impression_tracking.coffee
+++ components/analytics/impression_tracking.coffee
@@ -1,6 +1,6 @@
 _         = require 'underscore'
 sd        = require('sharify').data
-analytics = require('../../lib/analytics')
+analytics = require('../../lib/analytics.coffee')

 # very basic impression tracking that tracks all visible artworks when the $list is scrolled to
 # - only tracks VISIBLE artworks (useful for things like fillwidth rows where we don't display all artworks in the api response)


Heavily upgrade the preflight

Heavily upgrade the preflight

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -39,6 +39,7 @@
 util = require "util"

 {BowerSupport} = require "./bower"
+{Marshal} = require "./marshal"

 # This abstract base class service is an extension of the Screenplay
 # family that does some further environment initialization and set
@@ -52,3 +53,33 @@
     # can not be treated as a complete class implementation. This
     # mainly is used to exclude or account for abstract classes.
     @abstract yes
+
+    # A directive to mark the certain remote class or object to be
+    # included in the `Screenplay` context that is going to be emited
+    # and deployed on the client site. Basically, use this to bring
+    # in all the remote classes that you need to the remote call site.
+    @remote: (subject) ->
+        previous = @remotes or []
+        qualify = subject?.remote?.compile
+        noRemote = "the subject is not remote"
+        assert _.isFunction(qualify), noRemote
+        @remotes = previous.concat subject
+
+    # This server side method is called on the context prior to the
+    # context being compiled and flushed down to the client site. The
+    # method is wired in an asynchronous way for greater functionality.
+    # This is the place where you would be importing the dependencies.
+    prelude: (context, request, next) ->
+        remotes = @constructor.remotes or []
+        remotes = remotes.concat Marshal
+        for remote in _.unique remotes
+            @inject context, remote
+        return next()
+
+    # This block here defines a set of Bower dependencies that are
+    # going to be necessary no matter what sort of functionality is
+    # is going to be implemented. Most of these libraries required
+    # by the internal implementations of the various subcomponents.
+    @bower "eventemitter2"
+    @bower "bootstrap#3"
+    @bower "lodash"


if got 40001 err code, retry fetch_access_token

--- src/wx.coffee
+++ src/wx.coffee
@@ -619,6 +619,9 @@
           wx.user message.from_user_name, (err, user) ->
             if err
               console.error err
+              if err.errcode == 40001 || err.errcode == 42001
+                 redis_client.del 'WX:ACCESS_TOKEN'
+                 fetch_access_token()
               return res.status(500).end()
             process_message user


Accidentally committed unrelated thing

Accidentally committed unrelated thing

--- resources/assets/coffee/_classes/forum.coffee
+++ resources/assets/coffee/_classes/forum.coffee
@@ -259,6 +259,8 @@
         parent = @posts[0].parentNode
         parent.removeChild(post) for post in _.slice(@posts, toRemoveStart, toRemoveEnd)

+      @refreshLoadMoreLinks()
+
       # Restore scroll position after adding/removing posts.
       # Called after refreshLoadMoreLinks to allow header changes
       # to be included in calculation.
@@ -271,12 +273,10 @@
       osu.pageChange()
       link.dataset.failed = '0'

+    .always ->
+      link.classList.remove 'js-disabled'
     .fail ->
       link.dataset.failed = '1'
-    .always =>
-      @refreshLoadMoreLinks()
-
-      link.classList.remove 'js-disabled'

   jumpToSubmit: (e) =>


Removed not needed reassignment.

Removed not needed reassignment.

--- app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee
@@ -280,7 +280,7 @@

   render: ->

-    @el = @html App.view('generic/tabs')(
+    @html App.view('generic/tabs')(
       header: @header
       subHeader: @subHeader
       tabs: @tabs


Init version of i18n support.

Init version of i18n support.

--- app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
@@ -109,15 +109,15 @@
     @title 'Ticket Zoom ' + @ticket.number

     @configure_attributes_ticket = [
-      { name: 'ticket_state_id',    display: 'State',    tag: 'select',   multiple: false, null: true, relation: 'TicketState', default: 'new', class: 'span2', item_class: 'keepleft' },
-      { name: 'ticket_priority_id', display: 'Priority', tag: 'select',   multiple: false, null: true, relation: 'TicketPriority', default: '2 normal', class: 'span2', item_class: 'keepleft' },
+      { name: 'ticket_state_id',    display: 'State',    tag: 'select',   multiple: false, null: true, relation: 'TicketState', translate: true, class: 'span2', item_class: 'keepleft' },
+      { name: 'ticket_priority_id', display: 'Priority', tag: 'select',   multiple: false, null: true, relation: 'TicketPriority', translate: true, class: 'span2', item_class: 'keepleft' },
       { name: 'group_id',           display: 'Group',    tag: 'select',   multiple: false, null: true, relation: 'Group', class: 'span2', item_class: 'keepleft'  },
       { name: 'owner_id',           display: 'Owner',    tag: 'select',   multiple: false, null: true, relation: 'User', filter: @edit_form, nulloption: true, class: 'span2', item_class: 'keepleft' },
     ]
     form_ticket = @formGen( model: { configure_attributes: @configure_attributes_ticket, className: 'create' }, params: @ticket )
     @configure_attributes_article = [
 #      { name: 'from',                     display: 'From',     tag: 'input',    type: 'text', limit: 100, null: false, class: 'span8',  },
-      { name: 'ticket_article_type_id',   display: 'Type',        tag: 'select',   multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: '' },
+      { name: 'ticket_article_type_id',   display: 'Type',        tag: 'select',   multiple: false, null: true, relation: 'TicketArticleType', default: '9', translate: true, class: 'medium', item_class: '' },
       { name: 'to',                       display: 'To',          tag: 'input',    type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
       { name: 'cc',                       display: 'Cc',          tag: 'input',    type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
       { name: 'subject',                  display: 'Subject',     tag: 'input',    type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },


Remove whitespaces

Remove whitespaces

--- client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
+++ client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
@@ -2,7 +2,7 @@
   constructor:->
     super
     memberData = @getData()
-    @avatar = new AvatarStaticView
+    @avatar = new AvatarStaticView
       size      :
         width  : 90
         height : 90
@@ -36,7 +36,7 @@
     , memberData

     @skillTags = @putSkillTags()
-
+
     @followers = new KDView
       tagName     : 'a'
       attributes  :
@@ -58,11 +58,11 @@
     , memberData

     @sendMessageLink = new MemberMailLink {}, memberData
-
+
     memberData.locationTags or= []
     if memberData.locationTags.length < 1
-      memberData.locationTags[0] = "Earth"
-
+      memberData.locationTags[0] = "Earth"
+
     @location = new LocationView {},memberData
     @setListeners()
     @skillTags = new SkillTagGroup {}, memberData
@@ -91,7 +91,7 @@
     @template.update()

   putNick:(nick)-> "@#{nick}"
-
+
   pistachio:->
     """

@@ -104,7 +104,7 @@

-
{{#(profile.firstName)}} {{#(profile.lastName)}}

+
{{#(profile.firstName)}} {{#(profile.lastName)}}

{{> @location}}

@@ -121,7 +121,7 @@

{{ @utils.applyTextExpansions #(profile.about)}}

-
+

SKILLS{{> @skillTags}}

@@ -130,20 +130,20 @@
     {{> @trollSettings}}
     """

-
+
   putSkillTags:()->
     memberData = @getData()
-
+
     memberData.skillTags or= ['No Tags']
     skillTagHTML = "Skills"
     for skillTag in memberData.skillTags
-      if skillTag = 'No Tags'
+      if skillTag = 'No Tags'
         skillTagHTML += '
No Tags Yet. Add One.
'
-      else
+      else
         skillTagHTML += "#{skillTag}"
-
+
     skillTagHTML
-
+
   setListeners:->
     @sendMessageLink.registerListener
       KDEventTypes : "ToFieldHasNewInput"
@@ -160,14 +160,14 @@
     @sendMessageLink.registerListener
       KDEventTypes  : "AutoCompleteNeedsMemberData"
       listener      : @
-      callback      : (pubInst,event)=>
+      callback      : (pubInst,event)=>
         {callback,inputValue,blacklist} = event
         @fetchAutoCompleteForToField inputValue,blacklist,callback

     @sendMessageLink.registerListener
       KDEventTypes  : 'MessageShouldBeSent'
       listener      : @
-      callback      : (pubInst,{formOutput,callback})->
+      callback      : (pubInst,{formOutput,callback})->
         @prepareMessage formOutput,callback

   fetchAutoCompleteForToField:(inputValue,blacklist,callback)->
@@ -175,9 +175,9 @@
       callback accounts

   prepareMessage:(formOutput, callback)=>
-    {body, subject, recipients} = formOutput
+    {body, subject, recipients} = formOutput
     to = recipients.join ' '
-
+
     @sendMessage {to, body, subject}, (err, message)->
       new KDNotificationView
         title     : if err then "Failure!" else "Success!"


router & moment are unused

router & moment are unused

--- src/components/task-plan/builder/tasking.cjsx
+++ src/components/task-plan/builder/tasking.cjsx
@@ -1,8 +1,6 @@
 React = require 'react'
-Router = require 'react-router'
-_ = require 'underscore'
-moment = require 'moment-timezone'
-BS = require 'react-bootstrap'
+_     = require 'underscore'
+BS    = require 'react-bootstrap'

 TimeHelper = require '../../../helpers/time'
 TaskingDateTimes = require './tasking-date-times'


Higher debounce timeout on hotkey setting

--- extension/options.coffee
+++ extension/options.coffee
@@ -12,7 +12,7 @@
       @setHotkey char

   _setHotkey = (char) -> chrome.storage.sync.set "hotkey": char
-  setHotkey: _.debounce _setHotkey, 100
+  setHotkey: _.debounce _setHotkey, 250

   displayHotkey: ->
     chrome.storage.sync.get "hotkey", (storage) =>


A start of some regression tests (all passing, all example-y)

--- spec-e2e/basic-usage.coffee
+++ spec-e2e/basic-usage.coffee
@@ -5,11 +5,62 @@
   describe "passing tests", ->
     invariants.passingSpec()

-    Given -> createSpec """
-      describe 'foo', ->
-        Given -> @foo = 1
-        When -> @foo++
-        Then -> @foo == 2
-    """
-    WhenIRunTheSpec()
-    Then -> expect(@result.output).toMatch(/ok.*- foo then this.foo === 2/)
+    describe "GWT", ->
+        Given -> createSpec """
+          describe 'foo', ->
+            Given -> @foo = 1
+            When -> @foo++
+            Then -> @foo == 2
+        """
+        WhenIRunTheSpec()
+        Then -> expect(@result.output).toMatch(/ok.*- foo then this.foo === 2/)
+
+    describe "And", ->
+      Given -> createSpec """
+        describe '', ->
+          Given -> @things = []
+          And -> @things.push(1)
+          When -> @things.push(2)
+          And -> @things.reverse()
+          Then -> @things.length == 2
+          And -> @things[0] == 2
+      """
+      WhenIRunTheSpec()
+      Then -> expect(@result.output).toContain("then this.things.length === 2")
+      And -> expect(@result.output).toContain("# tests 1")
+
+    describe "Givens before Whens", ->
+      Given -> createSpec """
+        describe '', ->
+          When -> @result = 3 + @number
+
+          describe '1', ->
+            Given -> @number = 1
+            Then -> @result == 4
+
+          describe '2', ->
+            Given -> @number = 2
+            Then -> @result == 5
+      """
+      WhenIRunTheSpec()
+      Then -> expect(@result.output).toContain("1 then this.result === 4")
+      And -> expect(@result.output).toContain("2 then this.result === 5")
+
+    describe "Invariants", ->
+      Given -> createSpec """
+        describe '', ->
+          Invariant -> @foo ||= 0
+          Invariant -> @foo++
+
+          describe '1', ->
+            Then -> @foo == 1
+
+          describe '2', ->
+            Invariant -> @foo++
+            Then -> @foo == 2
+      """
+      WhenIRunTheSpec()
+      Then -> expect(@result.output).toContain("1 then this.foo === 1")
+      And -> expect(@result.output).toContain("2 then this.foo === 2")
+
+


Add font-family spec

Add font-family spec

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -515,6 +515,30 @@
       expect(editor.getGrammar().name).toBe 'JavaScript'
       editor.getBuffer().saveAs(path)
       expect(editor.getGrammar().name).toBe 'Plain Text'
+
+  describe "font family", ->
+    beforeEach ->
+      expect(editor.css('font-family')).not.toBe 'monaco'
+
+    it "sets the initial font family based on the value from config", ->
+      config.set("editor.fontFamily", "monaco")
+      newEditor = editor.splitRight()
+      expect(editor.css('font-family')).toBe 'monaco'
+      expect(newEditor.css('font-family')).toBe 'monaco'
+
+    describe "when the font family changes on the view", ->
+      it "updates the font family of editors and recalculates dimensions critical to cursor positioning", ->
+        rootView.attachToDom()
+        rootView.height(200)
+        rootView.width(200)
+
+        lineHeightBefore = editor.lineHeight
+        charWidthBefore = editor.charWidth
+        editor.setCursorScreenPosition [5, 6]
+        config.set("editor.fontFamily", "monaco")
+        expect(editor.charWidth).not.toBe charWidthBefore
+        expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
+        expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight

   describe "font size", ->
     it "sets the initial font size based on the value from config", ->


Update click and drag spec

Update click and drag spec

Stop relying on setInterval to trigger the scroll events in the spec. Instead simulate the mouse moving at the top of the screen. I found
testing the setInterval approach required so much mocking that it
wasn't worth it.

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -541,20 +541,23 @@
         expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)

       it "selects and scrolls if the mouse is dragged outside of the editor itself", ->
-        intervalFns = []
+        editor.vScrollMargin = 0
         editor.attachToDom(heightInLines: 5)
         editor.scrollToBottom()

-        spyOn(window, 'setInterval').andCallFake (fn) -> intervalFns.push(fn)
+        spyOn(window, 'setInterval').andCallFake ->
+
         # start
         editor.renderedLines.trigger mousedownEvent(editor: editor, point: [12, 0])
+        originalScrollTop = editor.scrollTop()

         # moving changes selection
-        $(document).trigger mousemoveEvent(editor: editor, pageX: 0, pageY: -15)
-        expect(editor.scrollTop()).toBe 4 * editor.lineHeight
-
-        # if cursor stays off screen, we keep moving / scrolling up
-        fn() for fn in intervalFns
+        $(document).trigger mousemoveEvent(editor: editor, pageX: 0, pageY: -1)
+        expect(editor.scrollTop()).toBe originalScrollTop - editor.lineHeight
+
+        # every mouse move selects more text
+        for x in [0..10]
+          $(document).trigger mousemoveEvent(editor: editor, pageX: 0, pageY: -1)

         expect(editor.scrollTop()).toBe 0


Modal works!

Modal works!

--- lineman/app/js/controllers/invitations_controller.coffee
+++ lineman/app/js/controllers/invitations_controller.coffee
@@ -1,5 +1,7 @@
-angular.module('loomioApp').controller 'InvitationController', ($scope, $modal) ->
+angular.module('loomioApp').controller 'InvitationsController', ($scope, $modal) ->
   $scope.openForm = ->
     modalInstance = $modal.open
       templateUrl: 'generated/templates/invitation_modal.html'
       controller: 'InvitationsModalController'
+      resolve:
+        group: -> $scope.group


Add certain flag for remodel item

Add certain flag for remodel item

--- models/report/remodel-item.coffee
+++ models/report/remodel-item.coffee
@@ -12,6 +12,7 @@
   consortLevel: Number
   consortCond: Number
   teitokuLv: Number
+  certain: Boolean

 RemodelItemRecord.virtual('date').get ->
   this._id.getTimestamp()


adjust icon size

adjust icon size

--- src/components/scores/concept-coach-cell.cjsx
+++ src/components/scores/concept-coach-cell.cjsx
@@ -37,7 +37,7 @@
         delayHide={0}
         overlay={tooltip}>
         <div className="worked">
-          <PieProgress size={25} value={pieValue} />
+          <PieProgress size={24} value={pieValue} />
         </div>
       </BS.OverlayTrigger>
     </div>


Scraping replygif.net with cheerio instead of hitting the whatcheer app

--- src/scripts/replygif.coffee
+++ src/scripts/replygif.coffee
@@ -2,7 +2,7 @@
 #   Makes ReplyGif easier to use. See http://replygif.net.
 #
 # Dependencies:
-#   None
+#   "cheerio": ">= 0.9.2"
 #
 # Configuration:
 #   None
@@ -11,25 +11,39 @@
 #   http://replygif.net/<id> - Embeds image from ReplyGif with that id.
 #   hubot replygif <keyword> - Embeds random ReplyGif with the keyword.
 #   hubot replygif me <keyword> - Same as `hubot replygif <keyword>`.
-#
+#
 # Notes:
-#   Thanks to What Cheer (@whatcheer) for providing the keyword lookup app.
+#   None
 #
 # Author:
-#   sumeetjain
+#   sumeetjain, meatballhat
+
+cheerio = require 'cheerio'

 module.exports = (robot) ->
   # Listen for someone to link to a ReplyGif and reply with the image.
   robot.hear /.*replygif\.net\/(i\/)?(\d+).*/i, (msg) ->
     id = msg.match[2]
     msg.send "http://replygif.net/i/#{id}#.gif"
-
+
   # Listen for a command to look up a ReplyGif by ID.
   robot.respond /replygif( me)? (\d+)/i, (msg) ->
     id = msg.match[2]
     msg.send "http://replygif.net/i/#{id}#.gif"
-
+
   # Listen for a command to look up a ReplyGif by tag.
   robot.respond /replygif( me)? (\D+)/i, (msg) ->
-    tag = msg.match[2]
-    msg.send "http://apps.whatcheerinc.com/replygif/#{tag}.gif"+    replyGifByTag(msg, msg.match[2])
+
+
+replyGifByTag = (msg, tag) ->
+  msg
+    .http("http://replygif.net/t/#{tag}")
+    .header('User-Agent: ReplyGIF for Hubot (+https://github.com/github/hubot-scripts)')
+    .get() (err, res, body) ->
+      msg.send msg.random getGifs(body)
+
+getGifs = (body) ->
+  $ = cheerio.load(body)
+  $('img.gif[src]').map (i, elem) ->
+    elem.attribs.src.replace(/thumbnail/, 'i')


fix test

fix test

--- apps/shows/test/meta.coffee
+++ apps/shows/test/meta.coffee
@@ -12,9 +12,9 @@
     it 'includes canonical url, twitter card, og tags, title, description', ->
       @html.should.containEql '<title>Art Gallery Shows and Museum Exhibitions | Artsy</title>'
       @html.should.containEql '<meta property="og:title" content="Art Gallery Shows and Museum Exhibitions | Artsy"/>'
-      @html.should.containEql '<meta name="description" content="Explore all shows on Artsy."/>'
-      @html.should.containEql '<meta property="og:description" content="Explore all shows on Artsy."/>'
-      @html.should.containEql '<meta property="twitter:description" content="Explore all shows on Artsy."/>'
+      @html.should.containEql '<meta name="description" content="Explore Artsy\'s comprehensive listing of current gallery shows and museum exhibitions from around the world."/>'
+      @html.should.containEql '<meta property="og:description" content="Explore Artsy\'s comprehensive listing of current gallery shows and museum exhibitions from around the world."/>'
+      @html.should.containEql '<meta property="twitter:description" content="Explore Artsy\'s comprehensive listing of current gallery shows and museum exhibitions from around the world."/>'
       @html.should.containEql '<link rel="canonical" href="http://localhost:5000/shows"/>'
       @html.should.containEql '<meta property="og:url" content="http://localhost:5000/shows"/>'
       @html.should.containEql '<meta property="og:image" content="/images/og_image.jpg"/>'


expanded API intro doc section

expanded API intro doc section

--- src/smc-util/message.coffee
+++ src/smc-util/message.coffee
@@ -29,30 +29,85 @@
 ###

 doc_intro = """
-
-## API messages
-
-These options appear on all API messages:
+## About the API
+
+### Purpose
+
+The purpose of the CoCalc API (application programming interface) is to make
+essential operations within the CoCalc platform available to automated
+clients. This allows embedding of CoCalc services within other products
+and customizing the external look and feel of the application.
+
+### Protocol and Data Format
+
+Each API command is invoked using an HTTPS PUT request.
+All commands support request parameters in JSON format, with request header
+`Content-Type: application/json`. Many commands (those that do not
+require lists or objects as parameters)
+also accept request parameters as key-value pairs, i.e.
+`Content-Type: application/x-www-form-urlencoded`.
+
+Responses are formatted as JSON strings.
+Note that it is possible for a request to fail and return
+a response code of 200. In that case, the response
+string may contain helpful information on the nature of
+the failure. In other cases, if the request cannnot
+be completed, a response code other than 200 may be
+returned, and the response body may be a
+generic HTML message rather than a JSON string.
+
+### Authentication
+
+A valid API key is required on all API requests.
+To obtain a key, log into
+CoCalc and click on Settings (gear icon next to user name at upper
+right), and look under `Account Settings`.
+With the `API key` dialogue, you can create a key,
+view a previously assigned key, generate a replacement key,
+and delete your key entirely.
+
+Your API key carries access privileges, just like your
+login and password.
+__Keep it secret.__
+Do not share your API key with others or post it in publicly accessible
+forums.
+
+### Additional References
+
+- The CoCalc PostgreSQL schema definition
+[src/smc-util/db-schema.coffee]
+(https://github.com/sagemathinc/cocalc/blob/master/src/smc-util/db-schema.coffee)
+has information on tables and fields used with the API `query` request.
+- The API test suite
+[src/smc-hub/test/api/]
+(https://github.com/sagemathinc/cocalc/tree/master/src/smc-hub/test/api)
+contains mocha unit tests for the API messages.
+- The CoCalc message definition file
+[src/smc-util/message.coffee]
+(https://github.com/sagemathinc/cocalc/blob/master/src/smc-util/message.coffee)
+contains the source for this guide.
+
+### API Message Reference
+
+The remainder of this guide explains the individual API endpoints.
+Each API request definition begins with the path of the
+URL used to invoke the request,
+for example `/api/v1/change_email_address`.
+The path name ends with the name of the request,
+for example, `change_email_address`.
+Following the path is the list of options.
+After options are one or more sample invocations
+illustrating format of the request as made with the `curl`
+command, and the format of the response.
+
+The following two options appear on all API messages
+(request parameters are often referred to
+as 'options' in the guide):

 - **event**: the command to be executed, for example "ping"
-- **id**: uuid for the API call, will be returned in response. If id not provided
-  in the API message, a random id will be generated and returned in the response
-
-Additional notes:
-
-- Options with default of "undefined" may be omitted.
-- A valid API key is required on all API calls, including ping.
-- The structure of the response to each API message is given in the
-  immediately following "message" object definition.
-- Some API messages, for example "query" and "get_usernames", require
-  options to be passed as a JSON object, as noted in message comments.
-- If JSON is not required for API message options, it is still valid.
-- If API message options are sent as JSON, the message must be sent with
-  a request header of "Content-Type: application/json".
-
-- See also: API mocha test suite:
-  https://github.com/sagemathinc/cocalc/tree/master/src/smc-hub/test/api
-
+- **id**: uuid for the API call, returned in response in most cases.
+If id is not provided in the API message, a random id will be
+generated and returned in the response.
 """

 misc     = require('./misc')
@@ -388,7 +443,6 @@
 will not be able to login, but will still be listed as collaborator
 or owner on projects which the user collaborated on or owned
 respectively.
-```
 """

 # hub --> client


fix typo in pull request #4038

fix typo in pull request #4038

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1478,14 +1478,14 @@

-                        Questions about billing, quotas, upgrades, ...
+                        Billing, quotas, and upgrades

-                        Project related questions.
+                        Projects

-

+


Remove Chai's should from deprecated tests

--- test/deprecated.coffee
+++ test/deprecated.coffee
@@ -1,20 +1,19 @@
 humanize = require '..'
-require('chai').should()
 assert = require 'assert'

 describe 'deprecated behavior', ->

   it 'does nothing when changing the language like before', ->
-    humanize(1000).should.equal '1 second'
+    assert.equal(humanize(1000), '1 second')
     humanize.language = 'es'
-    humanize(1000).should.equal '1 second'
+    assert.equal(humanize(1000), '1 second')
     delete humanize.language

   it 'does nothing when setting the language as a string', ->
-    humanize(1000, 'en').should.equal '1 second'
-    humanize(1000, 'es').should.equal '1 second'
-    humanize(1000, 'pt').should.equal '1 second'
-    humanize(1000, 'garbage').should.equal '1 second'
+    assert.equal(humanize(1000, 'en'), '1 second')
+    assert.equal(humanize(1000, 'es'), '1 second')
+    assert.equal(humanize(1000, 'pt'), '1 second')
+    assert.equal(humanize(1000, 'garbage'), '1 second')

   it 'removes old methods', ->
     assert.equal(humanize.componentsOf, undefined)


Make the unique application name test less strict

This allows us to skip an API regression and keep
master building while waiting for a fix to be
released.

Change-type: patch
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/application.spec.coffee
+++ tests/integration/models/application.spec.coffee
@@ -149,7 +149,13 @@
 						name: 'FooBar'
 						applicationType: 'microservices-starter'
 						deviceType: 'beaglebone-black'
-					m.chai.expect(promise).to.be.rejectedWith('Application name must be unique')
+					m.chai.expect(promise).to.be.rejected
+					.then (error) ->
+						m.chai.expect(error).to.have.property('code', 'BalenaRequestError')
+						m.chai.expect(error).to.have.property('statusCode', 404)
+						m.chai.expect(error).to.have.property('message').that.matches(/\bunique\b/i)
+						# TODO: re-enable once the API regression gets fixed
+						# m.chai.expect(error).to.have.property('message').that.contains('Application name must be unique')

 			describe 'balena.models.application.hasAny()', ->


**BREAKING** Expect a 409 status on unique constraint errors

Change-type: major
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/application.spec.coffee
+++ tests/integration/models/application.spec.coffee
@@ -167,7 +167,7 @@
 					m.chai.expect(promise).to.be.rejected
 					.then (error) ->
 						m.chai.expect(error).to.have.property('code', 'BalenaRequestError')
-						m.chai.expect(error).to.have.property('statusCode', 404)
+						m.chai.expect(error).to.have.property('statusCode', 409)
 						m.chai.expect(error).to.have.property('message').that.matches(/\bunique\b/i)
 						# TODO: re-enable once the API regression gets fixed
 						# m.chai.expect(error).to.have.property('message').that.contains('Application name must be unique')


:white_check_mark: Add tests for observeMinimaps method

--- spec/minimap-v4-spec.coffee
+++ spec/minimap-v4-spec.coffee
@@ -11,10 +11,10 @@
     workspaceElement = atom.views.getView(atom.workspace)
     jasmine.attachToDOM(workspaceElement)

-    waitsFor ->
+    waitsForPromise ->
       atom.workspace.open('sample.coffee')

-    waitsFor ->
+    waitsForPromise ->
       atom.packages.activatePackage('minimap').then (pkg) ->
         minimapPackage = pkg.mainModule

@@ -46,6 +46,20 @@
     it 'attaches a minimap element to the editor view', ->
       expect(editorElement.shadowRoot.querySelector('atom-text-editor-minimap')).toExist()

+  describe '::observeMinimaps', ->
+    [spy] = []
+    beforeEach ->
+      spy = jasmine.createSpy('observeMinimaps')
+      minimapPackage.observeMinimaps(spy)
+
+    it 'calls the callback with the existing minimaps', ->
+      expect(spy).toHaveBeenCalled()
+
+    it 'calls the callback when a new editor is opened', ->
+      waitsForPromise -> atom.workspace.open('other-sample.js')
+
+      runs -> expect(spy.calls.length).toEqual(2)
+
   describe '::deactivate', ->
     beforeEach ->
       minimapPackage.deactivate()


Adjust spec failure message

Adjust spec failure message

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -13,7 +13,7 @@
       toHaveScopes: (scopes) ->
         notText = if @isNot then "not" else ""
         this.message = (expected) =>
-          "Expected token \"#{@actual.value}\" to #{notText} have scopes \"#{expected}\". Instead found: [#{@actual.scopes.toString()}]"
+          "Expected token \"#{@actual.value}\" to #{notText} have scopes [\"#{expected}\"]. Instead found: [#{@actual.scopes.toString()}]"

         allScopesPresent = scopes.every (scope) =>
           return scope in @actual.scopes


Wait for package activation promise

Wait for package activation promise

--- spec/ui-watcher-spec.coffee
+++ spec/ui-watcher-spec.coffee
@@ -25,8 +25,10 @@

   describe "when a package stylesheet file changes", ->
     beforeEach ->
-      atom.packages.activatePackage("package-with-stylesheets-manifest")
-      uiWatcher = new UIWatcher()
+      waitsForPromise ->
+        atom.packages.activatePackage("package-with-stylesheets-manifest")
+      runs ->
+        uiWatcher = new UIWatcher()

     afterEach ->
       uiWatcher.destroy()
@@ -41,8 +43,10 @@

   describe "when a package does not have a stylesheet", ->
     beforeEach ->
-      atom.packages.activatePackage("package-with-index")
-      uiWatcher = new UIWatcher()
+      waitsForPromise ->
+        atom.packages.activatePackage("package-with-index")
+      runs ->
+        uiWatcher = new UIWatcher()

     afterEach ->
       uiWatcher.destroy()
@@ -53,8 +57,10 @@
   describe "when a package global file changes", ->
     beforeEach ->
       atom.config.set('core.themes', ["theme-with-ui-variables", "theme-with-multiple-imported-files"])
-      atom.themes.activateThemes()
-      uiWatcher = new UIWatcher()
+      waitsForPromise ->
+        atom.themes.activateThemes()
+      runs ->
+        uiWatcher = new UIWatcher()

     afterEach ->
       uiWatcher.destroy()
@@ -74,9 +80,11 @@
     pack = null
     beforeEach ->
       atom.config.set('core.themes', ["theme-with-index-less"])
-      atom.themes.activateThemes()
-      pack = atom.themes.getActiveThemes()[0]
-      uiWatcher = new UIWatcher()
+      waitsForPromise ->
+        atom.themes.activateThemes()
+      runs ->
+        uiWatcher = new UIWatcher()
+        pack = atom.themes.getActiveThemes()[0]

     afterEach ->
       uiWatcher.destroy()
@@ -97,9 +105,12 @@
     pack = null
     beforeEach ->
       atom.config.set('core.themes', ["theme-with-multiple-imported-files"])
-      atom.themes.activateThemes()
-      pack = atom.themes.getActiveThemes()[0]
-      uiWatcher = new UIWatcher()
+
+      waitsForPromise ->
+        atom.themes.activateThemes()
+      runs ->
+        uiWatcher = new UIWatcher()
+        pack = atom.themes.getActiveThemes()[0]

     afterEach ->
       uiWatcher.destroy()
@@ -121,17 +132,20 @@

     it "unwatches when a theme is deactivated", ->
       atom.config.set('core.themes', [])
-      expect(uiWatcher.watchedThemes["theme-with-multiple-imported-files"]).not.toBeDefined()
+      waitsFor ->
+        not uiWatcher.watchedThemes["theme-with-multiple-imported-files"]

     it "watches a new theme when it is deactivated", ->
       atom.config.set('core.themes', ["theme-with-package-file"])
-      expect(uiWatcher.watchedThemes["theme-with-package-file"]).toBeDefined()
+      waitsFor ->
+        uiWatcher.watchedThemes["theme-with-package-file"]

-      pack = atom.themes.getActiveThemes()[0]
-      spyOn(pack, 'reloadStylesheets')
+      runs ->
+        pack = atom.themes.getActiveThemes()[0]
+        spyOn(pack, 'reloadStylesheets')

-      expect(pack.name).toBe "theme-with-package-file"
+        expect(pack.name).toBe "theme-with-package-file"

-      watcher = uiWatcher.watchedThemes["theme-with-package-file"]
-      watcher.entities[2].emit('contents-changed')
-      expect(pack.reloadStylesheets).toHaveBeenCalled()
+        watcher = uiWatcher.watchedThemes["theme-with-package-file"]
+        watcher.entities[2].emit('contents-changed')
+        expect(pack.reloadStylesheets).toHaveBeenCalled()


TopicModeration: make if clause more readable

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -541,7 +541,7 @@

   follow: secure (client, options, callback) ->
     [callback, options] = [options, callback] unless callback
-    if @status is 'deleted' or @status is 'synonym'
+    if @status in ['synonym', 'deleted']
       return callback new KodingError "#{@status} topic cannot be followed"
     Followable = require '../traits/followable'
     Followable::follow.call this, client, options, callback


add 663 新型艤装の継続研究 & 854 戦果拡張任務！「Z作戦」前段作戦

--- assets/data/quest_goal.cson
+++ assets/data/quest_goal.cson
@@ -304,7 +304,7 @@
 ###
 # Quarterly
 ###
-426:
+426: #海上通商航路の警戒を厳とせよ
   type: 4
   fuzzy: true
   "mission_success@警備任務":
@@ -328,6 +328,14 @@
     mission: ['強行偵察任務']
     init: 0

+663: #新型艤装の継続研究
+  type: 4
+  "destory_item":
+    description: "廃棄"
+    slotitemId: [3]
+    required: 10
+    init: 0
+
 822: #沖ノ島海域迎撃戦
   type: 4
   "battle_boss_win_rank_s":
@@ -335,3 +343,27 @@
     maparea: [24]
     required: 2
     init: 0
+
+854: #戦果拡張任務！「Z作戦」前段作戦
+  type: 4
+  fuzzy: true
+  "battle_boss_win_rank_a@24":
+    description: "2-4"
+    maparea: [24]
+    required: 1
+    init: 0
+  "battle_boss_win_rank_a@61":
+    description: "6-1"
+    maparea: [61]
+    required: 1
+    init: 0
+  "battle_boss_win_rank_a@63":
+    description: "6-3"
+    maparea: [63]
+    required: 1
+    init: 0
+  "battle_boss_win_rank_a@64":
+    description: "6-4"
+    maparea: [64]
+    required: 1
+    init: 0


fix #65

fix #65

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -44,6 +44,9 @@
     @on 'mousedown', '.minimap-visible-area', @onDragStart

     @subscribe @paneView.model.$activeItem, @onActiveItemChanged
+    # Fixed item move to other pane.
+    @subscribe @paneView.model, 'item-removed', (item) -> item.off '.minimap'
+
     @subscribe @miniEditorView, 'minimap:updated', @updateMinimapView

     @subscribe $(window), 'resize:end', @onScrollViewResized


Text is immutable

Text is immutable

--- src/trix/models/text.coffee
+++ src/trix/models/text.coffee
@@ -17,71 +17,54 @@
   @fromJSON: (textJSON) ->
     pieces = for pieceJSON in textJSON
       Trix.Piece.fromJSON pieceJSON
-    new this pieces, textJSON.attributes
+    new this pieces

   @fromHTML: (html) ->
     Trix.HTMLParser.parse(html).getText()

-  constructor: (pieces = [], attributes = {}) ->
-    @editDepth = 0
+  constructor: (pieces = []) ->
     @pieceList = new Trix.PieceList pieces

-  edit = (fn) -> ->
-    @beginEditing()
-    fn.apply(this, arguments)
-    @endEditing()
+  copy: ->
+    @copyWithPieceList @pieceList

-  beginEditing: ->
-    @editDepth++
-    this
+  copyWithPieceList: (pieceList) ->
+    new @constructor pieceList.consolidate().toArray(), @attributes

-  endEditing: ->
-    if --@editDepth is 0
-      @pieceList.consolidate()
-      @delegate?.didEditText?(this)
-    this
-
-  edit: edit (fn) -> fn()
-
-  appendText: edit (text) ->
+  appendText: (text) ->
     @insertTextAtPosition(text, @getLength())

-  insertTextAtPosition: edit (text, position) ->
-    @pieceList.insertPieceListAtPosition(text.pieceList, position)
+  insertTextAtPosition: (text, position) ->
+    @copyWithPieceList @pieceList.insertPieceListAtPosition(text.pieceList, position)

-  removeTextAtRange: edit (range) ->
-    @pieceList.removePiecesInRange(range)
+  removeTextAtRange: (range) ->
+    @copyWithPieceList @pieceList.removePiecesInRange(range)

-  replaceTextAtRange: edit (text, range) ->
-    @removeTextAtRange(range)
-    @insertTextAtPosition(text, range[0])
+  replaceTextAtRange: (text, range) ->
+    @removeTextAtRange(range).insertTextAtPosition(text, range[0])

-  replaceText: edit (text) ->
-    @pieceList.mergePieceList(text.pieceList)
-
-  moveTextFromRangeToPosition: edit (range, position) ->
+  moveTextFromRangeToPosition: (range, position) ->
     return if range[0] <= position <= range[1]
     text = @getTextAtRange(range)
     length = text.getLength()
     position -= length if range[0] < position
-    @removeTextAtRange(range)
-    @insertTextAtPosition(text, position)
+    @removeTextAtRange(range).insertTextAtPosition(text, position)

-  addAttributeAtRange: edit (attribute, value, range) ->
+  addAttributeAtRange: (attribute, value, range) ->
     attributes = {}
     attributes[attribute] = value
     @addAttributesAtRange(attributes, range)

-  addAttributesAtRange: edit (attributes, range) ->
-    @pieceList.transformPiecesInRange range, (piece) ->
+  addAttributesAtRange: (attributes, range) ->
+    @copyWithPieceList @pieceList.transformPiecesInRange range, (piece) ->
       piece.copyWithAdditionalAttributes(attributes)

-  removeAttributeAtRange: edit (attribute, range) ->
-    @pieceList.transformPiecesInRange range, (piece) ->
+  removeAttributeAtRange: (attribute, range) ->
+    @copyWithPieceList @pieceList.transformPiecesInRange range, (piece) ->
       piece.copyWithoutAttribute(attribute)

-  setAttributesAtRange: edit (attributes, range) ->
-    @pieceList.transformPiecesInRange range, (piece) ->
+  setAttributesAtRange: (attributes, range) ->
+    @copyWithPieceList @pieceList.transformPiecesInRange range, (piece) ->
       piece.copyWithAttributes(attributes)

   getAttributesAtPosition: (position) ->
@@ -91,7 +74,7 @@
     @pieceList.getPieceListInRange(range)?.getCommonAttributes() ? {}

   getTextAtRange: (range) ->
-    new @constructor @pieceList.getPieceListInRange(range).toArray()
+    @copyWithPieceList @pieceList.getPieceListInRange(range)

   getStringAtRange: (range) ->
     @pieceList.getPieceListInRange(range).toString()
@@ -110,6 +93,8 @@
   resizeAttachmentToDimensions: (attachment, {width, height} = {}) ->
     if range = @getRangeOfAttachment(attachment)
       @addAttributesAtRange({width, height}, range)
+    else
+      this

   getLength: ->
     @pieceList.getLength()
@@ -135,9 +120,6 @@
   inspect: ->
     @pieceList.inspect()

-  copy: ->
-    new @constructor @pieceList.toArray()
-
   toString: ->
     @pieceList.toString()


Reuse Visit#cancelRender()

Reuse Visit#cancelRender()

--- src/turbolinks/visit.coffee
+++ src/turbolinks/visit.coffee
@@ -103,7 +103,7 @@
       @snapshotSaved = true

   render: (callback) ->
-    cancelAnimationFrame(@frame) if @frame
+    @cancelRender()
     @frame = requestAnimationFrame =>
       @frame = null
       callback.call(this)


rename vars

rename vars

--- src/user/login-gateway.cjsx
+++ src/user/login-gateway.cjsx
@@ -12,15 +12,13 @@

   openLogin: (ev) ->
     ev.preventDefault()
-    nY = Math.min(1000, window.screen.width - 20)
-    oY = Math.min(800, window.screen.height - 30)
-
+    width  = Math.min(1000, window.screen.width - 20)
+    height = Math.min(800, window.screen.height - 30)
     options = ["toolbar=no", "location=" + (window.opera ? "no" : "yes"),
       "directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no",
-      "width=" + nY,
-      "height=" + oY,
-      "top=" + (window.screen.height - oY) / 2,
-      "left=" + (window.screen.width - nY) / 2].join()
+      "width=" + width, "height=" + height,
+      "top="   + (window.screen.height - height) / 2,
+      "left="  + (window.screen.width - width)   / 2].join()

     loginWindow = window.open(@urlForLogin(), 'oxlogin', options)
     @setState({loginWindow})
@@ -40,7 +38,6 @@

   windowClosedCheck: ->
     return unless @isMounted()
-    console.log 'win close'
     if @state.loginWindow and @state.loginWindow.closed
       User.ensureStatusLoaded(true)
     else


Use Project::onDidChangePaths

Use Project::onDidChangePaths

--- lib/project-view.coffee
+++ lib/project-view.coffee
@@ -25,7 +25,7 @@

     @subscribeToConfig()

-    @subscribe atom.project, 'path-changed', =>
+    @subscribe atom.project.onDidChangePaths =>
       @reloadPaths = true
       @paths = null


Replaced placeholder text for text with better CTA

--- core/app/backbone/views/sub_comments/react_sub_comments_add.coffee
+++ core/app/backbone/views/sub_comments/react_sub_comments_add.coffee
@@ -41,7 +41,7 @@
     R.div className: 'discussion-evidenceish-content sub-comment-add spec-sub-comments-form',
       R.textarea
         className: "text_area_view",
-        placeholder: 'Comment...'
+        placeholder: 'Leave a reply'
         onChange: @updateText
         ref: 'textarea'
         value: @state.text


Removed DragList tests, better write them again #99

--- wzk/fx/DragList_test.coffee
+++ wzk/fx/DragList_test.coffee
@@ -1,60 +0,0 @@
-suite 'wzk.fx.DragList', ->
-  EVENTS = wzk.fx.DragList.EventType
-
-  draglist = null
-  item = null
-  el = null
-
-  mockEl = ->
-    el =
-      querySelector: ->
-        mockEl()
-      insertBefore: ->
-      events: {}
-      attachEvent: (event, target) ->
-        el.events[event] = target
-    el
-
-  document.createTextNode = ->
-    mockEl()
-
-  mockItem = (el, id = '1') ->
-    inDocument_: false
-    getParent: ->
-      null
-    getId: ->
-      id
-    setParent: ->
-    render_: ->
-    getElement: ->
-      el
-    exitDocument: ->
-
-  setup ->
-    item = mockItem mockEl()
-    el = mockEl()
-    draglist = new wzk.fx.DragList null, el
-
-  test 'Should dispatch ADD_ITEM event', (done) ->
-    goog.events.listen draglist, EVENTS.ADD_ITEM, (e) ->
-      done() if e.target is item
-    draglist.addItem item
-
-  test 'Should dispatch REMOVE_ITEM event', (done) ->
-    draglist.addItem item
-    goog.events.listen draglist, EVENTS.REMOVE_ITEM, (e) ->
-      done() if e.target is item
-    draglist.removeItem item
-
-  test 'Replace item should dispatch REMOVE_ITEM and ADD_ITEM event', (done) ->
-    itemRemoved = false
-    draglist.addItem item
-    newItem = mockItem mockEl(), '2'
-
-    goog.events.listen draglist, EVENTS.REMOVE_ITEM, (e) ->
-      itemRemoved = e.target is item
-
-    goog.events.listen draglist, EVENTS.ADD_ITEM, (e) ->
-      done() if e.target is newItem and itemRemoved
-
-    draglist.replaceItem item, newItem