fixed BUG #20463 【业务模块】创建完成在左边显示再弹出提示语

fixed BUG #20463 【业务模块】创建完成在左边显示再弹出提示语

--- lib/project/create-project.coffee
+++ lib/project/create-project.coffee
@@ -155,6 +155,7 @@

     # atom.notifications.addSuccess("Success: This is a notification");

+  # 新建业务模板应用
   newTemplateProject: (options) ->
     info = options.projectInfo
     fileName = if options.tmpType is 'news' then 'butter_newstemp' else ''
@@ -185,6 +186,7 @@
                         contentList['mainModule'] = contentJson['name']
                       fs.writeJson appConfigPath,contentList,null
               alert '应用创建成功'
+              @chameleonBox.closeView()
             Util.delete gfp,delSuccess

             appConfigPath = pathM.join info.appPath, desc.ProjectConfigFileName
@@ -199,7 +201,6 @@
             @modalPanel.item.children(".loading-mask").remove()
             atom.project.addPath(info.appPath)
             Util.rumAtomCommand 'tree-view:toggle' if $('.tree-view-resizer').length is 0
-            @chameleonBox.closeView()

         Util.copy @projectTempDir, info.appPath, copySuccess # 创建应用根目录成功后 将空白应用的应用内容复制到根目录


minor fix for user role dashboard

minor fix for user role dashboard

Former-commit-id: 34d42e59cbb0b03de01dd0037aaa7faaf2c518ff [formerly 46dd7599bdab15d6a8ff8fbdfdb9153494fa73a3] [formerly f444051e9f3cdae4b439398b9077e59499a95a9a [formerly f444051e9f3cdae4b439398b9077e59499a95a9a [formerly 4d2548ce6d8c03ade8fca5978c85daf454c57251]]]
Former-commit-id: 9ea26dbfc081784092c1451405fc1ad3f8c28065 [formerly 38cfab8792bf759b9c1695433893b1e18675fd22]
Former-commit-id: 03f57fff0fb505ebbe9236ea12509b922c216d66
Former-commit-id: 2083351779a8cff699bb5afa619856bd2f3c74ea

--- app/assets/javascripts/views/userdashboardview.coffee
+++ app/assets/javascripts/views/userdashboardview.coffee
@@ -78,7 +78,7 @@
           @users_by_roles_mask = new Backbone.Collection()
           @users_by_roles_mask.set((new ConsiderIt.User(user.user) for user in data.users_by_roles_mask) )

-          @change_context_finish(data)
+          @change_context_finish(_.extend(data, {users_by_roles_mask: @users_by_roles_mask}) )

       when 'analyze'
         data_uri = Routes.analytics_path()


fixing issue with repeated admin interface access

Former-commit-id: ca370d8ac63800279c33d7f6874ac12e91fcd9c8 [formerly 4135227bef4b05f5aaf4abbc8143871998917383] [formerly 11499a26e924321296302bf94380a3974a13b379 [formerly 11499a26e924321296302bf94380a3974a13b379 [formerly 5fb8f5371ef001086cf57ba587be789fdafb0bc9]]]
Former-commit-id: 0f37bd68e3a7d9b737f1da7189740e35d35af58f [formerly 6b0b014351d66ab5354934bfceec9ebafe40872e]
Former-commit-id: 4f0ca9a48430571960bbdc0351f7c1c89d626b08
Former-commit-id: af84afbe53bcd03cb5392629a627e7e78b233129

--- app/assets/javascripts/views/userdashboardview.coffee
+++ app/assets/javascripts/views/userdashboardview.coffee
@@ -216,6 +216,6 @@
   close : () ->
     @$el.slideUp()
     if @managing_dashboard_content_view
-      @managing_dashboard_content_view.remove()
+      @managing_dashboard_content_view.undelegateEvents()


Implementation of partials compile test.

--- hulk.coffee
+++ hulk.coffee
@@ -1,5 +1,21 @@
 exports.compile = (source='', options)->
 	hogan = require 'hogan.js'
 	compiled = hogan.compile source
+
+	require 'log'
+
+	partials = {}
+	# Get Partials #
+	partial_files = []
+	scanned = hogan.scan source
+	for item in scanned
+		if item.tag is '>'
+			partial_files.push item.n
+
+	if partial_files.length
+		fs = require 'fs'
+		for file in partial_files
+			partials[file] = fs.readFileSync(file).toString()  # TODO better way to read file, especially large ones.
+
 	return ->
-		return compiled.render options
+		return compiled.render options, partials


Fix loading indicator on conversation view

--- assets/javascripts/views/posts/conversation.js.coffee
+++ assets/javascripts/views/posts/conversation.js.coffee
@@ -20,7 +20,9 @@
     params = {
       post_types: TentStatus.config.post_types
     }
+    TentStatus.trigger 'loading:start'
     new HTTP 'GET', url, params, (post, xhr) =>
+      TentStatus.trigger 'loading:complete'
       return @render404() if xhr.status == 404
       return unless xhr.status == 200
       post = new TentStatus.Models.Post post
@@ -28,10 +30,12 @@
       @set 'post', post

       @on 'change:posts', @render
+      TentStatus.trigger 'loading:start'
       new HTTP 'GET', "#{TentStatus.config.tent_api_root}/posts", _.extend({
         limit: TentStatus.config.PER_PAGE
         mentioned_post: @post_id
       }, params), (posts, xhr) =>
+        TentStatus.trigger 'loading:complete'
         return unless xhr.status == 200
         since_id = posts[posts.length-1]?.id
         posts = new TentStatus.Collections.Posts posts
@@ -46,7 +50,9 @@
         @set 'parent_post', post
       else
         @on 'change:parent_post', @render
+        TentStatus.trigger 'loading:start'
         new HTTP 'GET', "#{TentStatus.config.tent_api_root}/posts/#{encodeURIComponent entity}/#{post_id}", null, (post, xhr) =>
+          TentStatus.trigger 'loading:complete'
           return unless xhr.status == 200
           post = new TentStatus.Models.Post post
           @set 'parent_post', post


fix fa

fix fa

--- lineman/config/application.coffee
+++ lineman/config/application.coffee
@@ -64,12 +64,7 @@
           expand: true
           cwd: 'vendor/bower_components/components-font-awesome/fonts'
           src: ['*.*']
-          dest: 'generated/fonts'
-        ,
-          expand: true
-          cwd: 'generated/'
-          src: ['*.html']
-          dest: 'dist/'
+          dest: 'dist/fonts'
         ]

   sass:


Remove unused matcher

Remove unused matcher

--- spec/javascripts/unit/admin/inventory_items/services/inventory_items_spec.js.coffee
+++ spec/javascripts/unit/admin/inventory_items/services/inventory_items_spec.js.coffee
@@ -7,10 +7,6 @@
     module ($provide) ->
       $provide.value 'inventoryItems', inventoryItems
       null
-
-    this.addMatchers
-      toDeepEqual: (expected) ->
-        return angular.equals(this.actual, expected)

     inject ($q, _$httpBackend_, _InventoryItems_, _InventoryItemResource_) ->
       InventoryItems = _InventoryItems_


Fix arguments to slot.*Record to actual record

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -859,7 +859,7 @@

       remove: ->
         messages.clear(this)
-        slot.removeRecord(@rec, this).then (r) =>
+        slot.removeRecord(@record, this).then (r) =>
             return unless r
             records.remove(this)
             unchoose() if $scope.current == this
@@ -924,7 +924,7 @@

       savePosition: () ->
         messages.clear(this)
-        slot.moveRecord(@rec, @rec.segment, this).then (r) =>
+        slot.moveRecord(@record, @rec.segment, this).then (r) =>
             if r && @empty
               records.remove(this)
               unchoose() if this == $scope.current


add a metric for append-pack

add a metric for append-pack

--- app/coffee/PackManager.coffee
+++ app/coffee/PackManager.coffee
@@ -143,6 +143,7 @@
 		if lastUpdate.expiresAt and temporary
 			update.$set.expiresAt = new Date(Date.now() + 7 * DAYS)
 		logger.log {project_id, doc_id, lastUpdate, newUpdates}, "appending updates to existing pack"
+		Metrics.inc("append-pack-" + if temporary then "temporary" else "permanent")
 		db.docHistory.findAndModify {query, update,	new:true, fields:{meta:1,v_end:1}}, callback

 	# Retrieve all changes for a document


shell.spec: fixed typo

shell.spec: fixed typo

--- coffee/test/shells/shell.spec.coffee
+++ coffee/test/shells/shell.spec.coffee
@@ -6,7 +6,7 @@
 describe 'acorn.shells.Shell', ->
   Shell = acorn.shells.Shell

-  it 'should be part of acorn.specs', ->
+  it 'should be part of acorn.shells', ->
     expect(acorn.shells.Shell).toBeDefined()

   it 'should derive from Backbone.Model', ->


fixed a latent bug

fixed a latent bug

--- coffeequate/src/operators/Pow.coffee
+++ coffeequate/src/operators/Pow.coffee
@@ -248,7 +248,7 @@
 				unless subbed
 					left = @children.left.copy()
 			else if @children.left.sub?
-				left = @children.left.sub(substitutions, uncertaintySubstitutions)
+				left = @children.left.sub(substitutions, uncertaintySubstitutions, equivalencies)
 			else
 				left = @children.left.copy()

@@ -263,7 +263,7 @@
 				unless subbed
 					right = @children.right.copy()
 			else if @children.right.sub?
-				right = @children.right.sub(substitutions, uncertaintySubstitutions)
+				right = @children.right.sub(substitutions, uncertaintySubstitutions, equivalencies)
 			else
 				right = @children.right.copy()


Make links work when on non default ip:port

--- js/ZeroChat.coffee
+++ js/ZeroChat.coffee
@@ -100,10 +100,10 @@
         replacedText = replacedText.replace(replacePattern3, '$1')

         # // replace 0net:// URL href back to http://127.0.0.1:43110
-        replacedText = replacedText.replace(replacePattern4, '0net://$1')
+        replacedText = replacedText.replace(replacePattern4, '0net://$1')

         # // rewrite link href and replace //0mail with @zeroid.bit
-        replacedText = replacedText.replace(replacePattern5, '$1@zeroid.bit')
+        replacedText = replacedText.replace(replacePattern5, '$1@zeroid.bit')

         return replacedText


Added comments.

Added comments.

--- client/accounts.coffee
+++ client/accounts.coffee
@@ -7,10 +7,14 @@

 # To close login buttons dialog box when clicking or focusing somewhere outside
 $(document).click (e) ->
+  # originalEvent is defined only for native events, but we are triggering
+  # click manually as well, so originalEvent is not always defined
   Accounts._loginButtonsSession.closeDropdown() unless e.originalEvent?.dialogBoxEvent
   return # Make sure CoffeeScript does not return anything

 $(document).focus (e) ->
+  # originalEvent is defined only for native events, but we are triggering
+  # click manually as well, so originalEvent is not always defined
   Accounts._loginButtonsSession.closeDropdown() unless e.originalEvent?.dialogBoxEvent
   return # Make sure CoffeeScript does not return anything


wip: another failing fold test

wip: another failing fold test

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -2424,6 +2424,18 @@
     beforeEach ->
       editor.attachToDom()

+    describe "???", ->
+      fit "won't fuck up", ->
+        setEditorHeightInLines(editor, 50)
+        editor.setBuffer(new Buffer(require.resolve('fixtures/two-hundred.txt')))
+        editor.toggleSoftWrap()
+        fold8 = editor.createFold([[163, 3], [169, 33]])
+        fold9 = editor.createFold([[169, 34], [184, 10]])
+        editor.destroyFold(fold8.id)
+
+        editor.scrollBottom(editor.scrollView.prop('scrollHeight'))
+
+
     describe "when a fold-selection event is triggered", ->
       it "folds the selected text and moves the cursor to just after the placeholder, then treats the placeholder as a single character", ->
         editor.getSelection().setBufferRange(new Range([4, 29], [7, 4]))


Remove logging of found package

Remove logging of found package

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -272,7 +272,6 @@

     pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
     if pack?
-      console.log(pack)
       if pack.urlMain
         packagePath = @packages.resolvePackagePath(packageName)
         bootstrapScript = path.resolve(packagePath, pack.urlMain)


Add disposable for removing debouncedSaveState listener

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -4,7 +4,7 @@

 _ = require 'underscore-plus'
 {deprecate} = require 'grim'
-{CompositeDisposable, Emitter} = require 'event-kit'
+{CompositeDisposable, Disposable, Emitter} = require 'event-kit'
 fs = require 'fs-plus'
 {mapSourcePosition} = require 'source-map-support'
 Model = require './model'
@@ -121,11 +121,6 @@
   constructor: (params={}) ->
     {@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params

-    debouncedSaveState = _.debounce((=> @saveState()), @saveStateDebounceInterval)
-    @document.addEventListener('mousedown', debouncedSaveState, true)
-    @document.addEventListener('keydown', debouncedSaveState, true)
-
-
     @loadTime = null
     {devMode, safeMode, resourcePath} = @getLoadSettings()

@@ -205,6 +200,7 @@
     @registerDefaultViewProviders()

     @installUncaughtErrorHandler()
+    @attachSaveStateListeners()
     @installWindowEventHandler()

     @observeAutoHideMenuBar()
@@ -217,6 +213,14 @@
       ipc.send('check-portable-home-writable', responseChannel)

     checkPortableHomeWritable()
+
+  attachSaveStateListeners: ->
+    debouncedSaveState = _.debounce((=> @saveState()), @saveStateDebounceInterval)
+    @document.addEventListener('mousedown', debouncedSaveState, true)
+    @document.addEventListener('keydown', debouncedSaveState, true)
+    @disposables.add new Disposable =>
+      @document.removeEventListener('mousedown', debouncedSaveState, true)
+      @document.removeEventListener('keydown', debouncedSaveState, true)

   setConfigSchema: ->
     @config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}


fix: fixed broken tube normals

fix: fixed broken tube normals

--- 3d/tubing.coffee
+++ 3d/tubing.coffee
@@ -113,15 +113,15 @@
       curIx += 6
       return

+    shape = @tubeShape
     r = @tubeRadius

-    x_pn=@prevNormals[tubeIndex/2]
-    y_pn=@prevNormals[tubeIndex/2+1]
-    z_pn=@prevNormals[tubeIndex/2+2]
+    normalsIndex = (tubeIndex/2*3) | 0

-    #x0 = xys[0][i]
-    #y0 = xys[0][i]
-    dz = @stepZ
+    x_pn=@prevNormals[normalsIndex]
+    y_pn=@prevNormals[normalsIndex+1]
+    z_pn=@prevNormals[normalsIndex+2]
+
     for iz in [ 1 ... xys.length-1 ]
       xy = xys[iz]
       xyPrev = xys[iz-1]
@@ -162,21 +162,13 @@

       #now calculate the third vector, as a X-product of
       # (xn1, yn1, zn1) X (dx, dy, dz)
-      # -dz*yn1*i +dz*xn1*j+ k*(dx*yn1-dy*xn1)
       xn2 = dy*zn1-dz*yn1
       yn2 = dz*xn1-dx*zn1
       zn2 = dx*yn1-dy*xn1

-      #if Math.abs(xn2**2+yn2**2+zn2**2 - 1) > 1e-3
-      #  throw new Error "Big difference N2"
-      #if Math.abs(xn1**2+yn1**2+zn1**2 - 1) > 1e-3
-      #  throw new Error "Big difference N1"
-
       vindex = curV / 3 | 0

-      #and push shape of the tube section
-      shape = @tubeShape
-
+      #and push shape of the tube section
       for i in [0 ... shape.length] by 2
         vx = shape[i]
         vy = shape[i+1]
@@ -191,9 +183,9 @@
           j1 = (j+1)%tubeEdges
           pushQuad vindex-tubeEdges+j, vindex+j, vindex-tubeEdges+j1, vindex+j1

-    @prevNormals[tubeIndex/2] = x_pn
-    @prevNormals[tubeIndex/2+1] = y_pn
-    @prevNormals[tubeIndex/2+2] = z_pn
+    @prevNormals[normalsIndex] = x_pn
+    @prevNormals[normalsIndex+1] = y_pn
+    @prevNormals[normalsIndex+2] = z_pn
     if curV isnt vs.length then throw new Error "Not all vertices filled"

     if curIx isnt ixs.length


Fix/update lazy visualizer demo

Fix/update lazy visualizer demo

--- demos/visualizer-lazy/Grid.coffee
+++ demos/visualizer-lazy/Grid.coffee
@@ -1,4 +1,7 @@
 'use strict'
+
+
+S = bef.Symbols

 colors =
@@ -62,22 +65,26 @@

 getArrowImages = (size) ->
-	'^': getArrowImage size, 0
-	'<': getArrowImage size, -Math.PI / 2
-	'v': getArrowImage size, Math.PI
-	'>': getArrowImage size, Math.PI / 2
+	images = {}
+
+	images[S.UP] = getArrowImage size, 0
+	images[S.LEFT] = getArrowImage size, -Math.PI / 2
+	images[S.DOWN] = getArrowImage size, Math.PI
+	images[S.RIGHT] = getArrowImage size, Math.PI / 2
+
+	images

 directions = [
-	{ char: '<', offset: { x: 0, y: 1 } }
-	{ char: '^', offset: { x: 1, y: 0 } }
-	{ char: 'v', offset: { x: 1, y: 2 } }
-	{ char: '>', offset: { x: 2, y: 1 } }
+	{ charCode: S.LEFT,  offset: { x: 0, y: 1 } }
+	{ charCode: S.UP,    offset: { x: 1, y: 0 } }
+	{ charCode: S.DOWN,  offset: { x: 1, y: 2 } }
+	{ charCode: S.RIGHT, offset: { x: 2, y: 1 } }
 ]

-directionsIndexed = directions.reduce (ret, { char, offset }) ->
-		ret.set char, offset
+directionsIndexed = directions.reduce (ret, { charCode, offset }) ->
+		ret.set charCode, offset
 		ret
 	, new Map

@@ -99,16 +106,16 @@

 	for i in [0...@playfield.width]
 		for j in [0...@playfield.height]
-			char = @playfield.getAt i, j
-
-			if char in ['^', '<', 'v', '>'] and (@pathSet.getStartingFrom i, j, '')?
-				offset = directionsIndexed.get char
-				@hitRegions.push getCellRegion i, j, offset.x, offset.y, char
+			charCode = @playfield.getAt i, j
+
+			if charCode in [S.UP, S.LEFT, S.DOWN, S.RIGHT] and (@pathSet.getStartingFrom i, j, '')?
+				offset = directionsIndexed.get charCode
+				@hitRegions.push getCellRegion i, j, offset.x, offset.y, charCode
 			else
 				for dir in directions
-					if (@pathSet.getStartingFrom i, j, dir.char)?
+					if (@pathSet.getStartingFrom i, j, dir.charCode)?
 						offset = dir.offset
-						@hitRegions.push getCellRegion i, j, offset.x, offset.y, dir.char
+						@hitRegions.push getCellRegion i, j, offset.x, offset.y, dir.charCode

 	return

@@ -168,13 +175,12 @@
 	@con2d.fillStyle = colors.text
 	for i in [0...@playfield.width]
 		for j in [0...@playfield.height]
-			charRaw = @playfield.getAt i, j
-			charCode = charRaw.charCodeAt 0
+			charCode = @playfield.getAt i, j

 			if charCode > 0
 				charPretty = if 32 <= charCode <= 126
 					@con2d.font = fonts.normal
-					charRaw
+					String.fromCharCode charCode
 				else
 					@con2d.font = fonts.small
 					"##{charCode}"


Better event presence check

Better event presence check

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -135,8 +135,8 @@
     if @state.beatmapsetDiscussion.updated_at?
       params.last_updated = moment(@state.beatmapsetDiscussion.updated_at).unix()

-    if @state.beatmapsetDiscussion.beatmapset_events?
-      params.last_updated = _.max [params?.last_updated, moment(_.last(@state.beatmapsetDiscussion.beatmapset_events)?.created_at).unix()]
+    if !_.isEmpty @state.beatmapsetDiscussion.beatmapset_events
+      params.last_updated = _.max [params?.last_updated, moment(_.last(@state.beatmapsetDiscussion.beatmapset_events).created_at).unix()]

     @checkNewAjax = $.get laroute.route('beatmapsets.discussion', beatmapset: @state.beatmapset.id), params
     .done (data, _textStatus, xhr) =>


Ensure performance graph to display at least rank 25

--- resources/assets/coffee/react/profile-page/performance.coffee
+++ resources/assets/coffee/react/profile-page/performance.coffee
@@ -20,6 +20,7 @@

 class ProfilePage.Performance extends React.Component
   componentDidMount: ->
+    @minimumRankTick = -24
     @_rankHistory()

   componentDidUpdate: ->
@@ -32,6 +33,7 @@
     return [] unless data.length

     rankRange = d3.extent data, (d) => d.y
+    rankRange[0] = Math.min rankRange[0], @minimumRankTick

     @_allTicks ||= [-1, -2.5, -5]


fiddling with now_playing, trying to make sure removing a session in view won't break the carousel


Settings link is an icon

Settings link is an icon

--- app/partials/account-bar.cjsx
+++ app/partials/account-bar.cjsx
@@ -25,11 +25,10 @@
   render: ->

       {' '}
-      <Link to="user-profile" params={name: @props.user.display_name}>{@props.user.display_name}</Link>{' '}
-
-      <Link to="inbox"> </Link>
-      <button type="button" className="pill-button" onClick={@handleSignOutClick}>Sign out</button>{' '}
-      <Link to="settings" className="pill-button">Settings</Link>
+      <Link to="user-profile" params={name: @props.user.display_name}>{@props.user.display_name}</Link>
+      <Link to="inbox"></Link>
+      <Link to="settings"></Link>
+      <button type="button" className="pill-button" onClick={@handleSignOutClick}>Sign out</button>

   handleSignOutClick: ->


Fix mobile nav

Fix mobile nav

--- app/partials/main-header.cjsx
+++ app/partials/main-header.cjsx
@@ -1,6 +1,6 @@
 counterpart = require 'counterpart'
 React = require 'react'
-{IndexLink, Link} = require 'react-router'
+{IndexLink, Link, History} = require 'react-router'
 ZooniverseLogo = require './zooniverse-logo'
 Translate = require 'react-translate-component'
 LoadingIndicator = require '../components/loading-indicator'
@@ -12,6 +12,7 @@
 TriggeredModalForm = require 'modal-form/triggered'
 debounce = require 'debounce'
 HamburgerIcon = require '../partials/hamburger-icon'
+PassHistoryContext = require '../components/pass-history-context'

 counterpart.registerTranslations 'en',
   mainNav:
@@ -30,6 +31,8 @@

 module.exports = React.createClass
   displayName: 'MainHeader'
+
+  mixins: [History]

   getDefaultProps: ->
     user: null
@@ -106,7 +109,9 @@
           <TriggeredModalForm
             triggerProps={className: "hamburger-modal-trigger"}
             trigger={<HamburgerIcon />}>
-            {@links()}
+            <PassHistoryContext {...@props} context={history: @history}>
+              {@links()}
+            </PassHistoryContext>
           </TriggeredModalForm>}

       </div>


removed "a" tags from topics modal's posts and followers

--- client/landing/app/Applications/Topics.kdapplication/Views/TopicsListItemView.coffee
+++ client/landing/app/Applications/Topics.kdapplication/Views/TopicsListItemView.coffee
@@ -212,12 +212,10 @@
         {{> @titleLink}}

-
-            {{#(counts.tagged) or 0}} Posts
+            {{#(counts.tagged) or 0}} Posts

-
-            {{#(counts.followers) or 0}} Followers
+            {{#(counts.followers) or 0}} Followers


Fix disconnect detection

Fix disconnect detection

--- src/board/wakayamarbboard.coffee
+++ src/board/wakayamarbboard.coffee
@@ -193,11 +193,18 @@
   @inheritdoc Board#disconnect
   ###
   disconnect: ->
-    return @errorNotConnected() unless @_serial?
-    return @_serial.close().then(=>
-      @_serial = null
+    oldSerial = @_serial
+    return @errorNotConnected() unless oldSerial?
+    @_serial = null
+    return Promise.resolve(
+    ).then(=>
+      return oldSerial.close?()
+    ).then(=>
       return super()
-    )
+    ).catch((error) =>
+      @_serial = oldSerial
+      return Promise.reject(error)
+    ) # return Promise.resolve().then()...

   ###*
   @inheritdoc Board#getBoardInfo
@@ -372,8 +379,10 @@
   _closeHandler: ->
     reject = @_waiter?.reject
     @_waiter = null
-    @disconnect() if @_serial?
     reject?()
+    if @_serial?
+      @_serial = {}
+      @disconnect()
     return

   ###*


fix targets etc

fix targets etc

--- gulp-nav.coffee
+++ gulp-nav.coffee
@@ -40,90 +40,84 @@

     through (file, encoding, transformCallback) ->
       # if vinyl objects have different properties, take first defined
-      source = (file[prop] for prop in sources).reduce (x, y) -> x ?= y
+      source = (file[source] for source in sources).reduce (x, y) -> x ?= y
       source ?= file    # just look for title and order on the vinyl obj itself
-      title = (source[prop] for prop in titles).reduce (x, y) -> x ?= y
-      order = (source[prop] for prop in orders).reduce (x, y) -> x ?= y
+      title = (source[title] for title in titles).reduce (x, y) -> x ?= y
+      order = (source[order] for order in orders).reduce (x, y) -> x ?= y
       # insert new nav into the tree
-      nav = insertNav file.relative, hrefExtension, title, order
+      nav = insertNavIntoTree file.relative, hrefExtension, title, order
       # set properties of vinyl object
-      for prop in []    # XXX use targets!
-        tgt = file
-        for part in (prop.split '.')[...-1]
-          console.log part
-          tgt[part] ?= {}
-          tgt = tgt[part]
-        tgt[(prop.split '.')[-1]] = nav
-      file['data'] =    # XXX use targets!
-        nav: nav
-      file['nav'] = nav
+      for target in targets
+        obj = file
+        [props..., lastProp] = target.split '.'         # handle nested targets
+        obj = obj[prop] ?= {} for prop in props
+        obj[lastProp] = nav
       # delay until we've seen them all...
       files.push file
       transformCallback()
     , (flushCallback) ->
       # ...and now we've seen them all
       @push file for file in files
+      console.log file for file in files
       #console.log (require 'util').inspect navTree, depth: null
       flushCallback()

 navTree =
+  parent: null
+  children: {}
   exists: no
   title: null
-  parent: null
-  children: {}

 orderGen = 9999

-insertNav = (relativePath, extension, title, order) ->
+insertNavIntoTree = (relativePath, extension, title, order) ->
   _path = path.resolve '/', relativePath
-    .replace /index\.[^/]+$/, ''
-    .replace /\.[^./]+$/, '.' + extension
-    .split /([^/]*\/)/
+    .replace /index\.[^/]+$/, ''              # index identified with directory
+    .replace /\.[^./]+$/, '.' + extension     # e.g. '.jade' -> '.html'
+    .split /([^/]*\/)/                        # e.g. '/a/b' -> ['/', 'a/', 'b']
     .filter (element) -> element isnt ''
   current = navTree
   for element in _path
     current = current.children[element] ?=   # recurse down, filling in missing
+      parent: current
       children: {}
-      parent: current
       exists: no                             # for directories without an index
       title: path.basename element.replace /\/?index[^/]*$/, ''
         .toLowerCase()
-        .replace /\.[^.]*$/, ''                  # remove extension
-        .replace /(?:^|[-._])[a-z]/g, (first) -> # capitalize words
-          first.toUpperCase()
-        .replace /[-._]/g, ' '                   # change punctuation to spaces
-        .replace /^$/, '/'                       # root needs a title too
+        .replace /\.[^.]*$/, ''                        # remove extension
+        .replace /(?:^|[-._])[a-z]/g, (first) ->
+          first.toUpperCase()                          # capitalize each word
+        .replace /[-._]/g, ' '                         # punctuation to spaces
+        .replace /^$/, '/'                             # root needs a title too
       order: orderGen++
   current.exists = yes                            # this resource *does* exist!
   if title
     current.title = title
   if order
     current.order = order
-  visitednav current, _path
+  navInContext current, _path

-# this function creates the objects that we actually put
-visitednav = (nav, context) ->
+# create nav object with
+navInContext = (nav, context) ->
   nav and Object.defineProperties
     title: nav.title
     href: if nav.exists
       webPath.relative context[0], webPath.resolve context...
     active: context.length is 1
   ,
-    parent:    # these properties are accessors in order to get lazy evaluation
-      enumerable: yes                 # these properties should be easy to find
-      get: ->
-        #console.log nav.parent, context
-        visitednav nav.parent, context.concat '.'
+    parent:
+      enumerable: yes             # these properties should be easy to find
+      get: ->                     # they're accessors because we need lazy eval
+        navInContext nav.parent, context.concat if context.slice(-1).slice(-1) is '/' then '..' else '.'
     children:
       enumerable: yes
       get: ->
         sorted = ([name, child] for name, child of nav.children)
           .sort ([_, a], [__, b]) ->
             a.order - b.order
-        (for [name, child] in sorted
-          visitednav child, context.concat name + '/')
+        (navInContext child, context.concat name for [name, child] in sorted)
     siblings:
       enumerable: yes
       get: ->
         for name, sibling of nav.parent.children
-          visitednav sibling, context.concat name
+          navInContext sibling, context.concat name


Make default gulp task compile and test

Make default gulp task compile and test

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,4 +20,4 @@
   gulp.watch([src, test], ['test'])
 )

-gulp.task('default', ['test'])
+gulp.task('default', ['compile', 'test'])


change gulpfile

change gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,20 +1,13 @@
-$            = (require 'gulp-load-plugins')()
-config       = require './config.coffee'
-del          = require 'del'
-gulp         = require 'gulp'
-gulpsync     = $.sync gulp
-path         = require 'path'
-htmlincluder = require 'gulp-htmlincluder'
-
-gulp.task 'clean', ->
-  del config.prod_path, force: true
+$        = (require 'gulp-load-plugins')()
+gulp     = require 'gulp'
+gulpsync = $.sync gulp
+w3cjs    = require 'w3cjs'
+marked   = require './helpers/marked'
+hash_gen = require './helpers/hash_gen'
+config   = require './config.coffee'
+path     = require 'path'

 gulp.task 'gen_js', ->
-  gulp.src path.join config.dev_path_coffee, '*.coffee'
-    .pipe $.coffee bare: true
-    .pipe gulp.dest config.prod_path_js
-
-gulp.task 'gen_js_live', ->
   gulp.src path.join config.dev_path_coffee, '*.coffee'
     .pipe $.coffee bare: true
     .pipe gulp.dest config.prod_path_js
@@ -38,66 +31,40 @@
       cascade: true
     .pipe gulp.dest config.prod_path_css

-gulp.task 'gen_css_live', ->
-  gulp.src path.join config.dev_path_sass, '*.sass'
-    .pipe $.sass indentedSyntax: true
-    .pipe $.autoprefixer
-      browsers: [
-        'Firefox ESR'
-        'Firefox 14'
-        'Opera 10'
-        'last 2 version'
-        'safari 5'
-        'ie 8'
-        'ie 9'
-        'opera 12.1'
-        'ios 6'
-        'android 4'
-      ]
-      cascade: true
-    .pipe gulp.dest config.prod_path_css
-
-gulp.task 'gen_markdown', ->
-  gulp.src path.join config.database_path, '*.md'
-    .pipe $.markdown()
-    .pipe gulp.dest config.dev_path
-
 gulp.task 'gen_html', ->
-  gulp.src path.join config.dev_path, '*.html'
-    .pipe htmlincluder()
-    .pipe gulp.dest config.prod_path
-  return
-
-gulp.task 'gen_html_live', ->
-  gulp.src path.join config.dev_path, '*.html'
-    .pipe htmlincluder()
+  gulp.src path.join config.dev_path, 'index.ejs'
+    .pipe $.ejs {marked: marked, env: process.env.NODE_ENV, hash_gen: hash_gen}, {}, ext: '.html'
     .pipe gulp.dest config.prod_path
   return

 gulp.task 'copy', ->
-  gulp.src path.join(config.dev_path_css, '**/*'),   base: config.dev_path
+  gulp.src path.join(config.dev_path_css,     '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
-  gulp.src path.join(config.dev_path_img, '**/*'),   base: config.dev_path
+  gulp.src path.join(config.dev_path_img,     '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
-  gulp.src path.join(config.dev_path_favicon, '**/*'),   base: config.dev_path
+  gulp.src path.join(config.dev_path_favicon, '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
-  gulp.src path.join(config.dev_path_js, '**/*'),    base: config.dev_path
+  gulp.src path.join(config.dev_path_js,      '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
-  gulp.src path.join(config.dev_path_json, '**/*'),  base: config.dev_path
+  gulp.src path.join(config.dev_path_json,    '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
-  gulp.src path.join(config.dev_path_fonts, '**/*'), base: config.dev_path
+  gulp.src path.join(config.dev_path_fonts,   '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
-  gulp.src path.join(config.dev_path_json, '**/*'),  base: config.dev_path
+  gulp.src path.join(config.dev_path_json,    '**/*'), base: config.dev_path
     .pipe gulp.dest config.prod_path
   gulp.src path.join config.dev_path, 'CNAME'
     .pipe gulp.dest config.prod_path
+  gulp.src path.join(config.dev_path, 'favicon.ico')
+    .pipe gulp.dest config.prod_path
+  #gulp.src path.join(config.dev_path, 'robots.txt')
+    #.pipe gulp.dest config.prod_path
+  #gulp.src path.join(config.dev_path, 'sitemap.xml')
+    #.pipe gulp.dest config.prod_path
   return

 gulp.task 'default', gulpsync.sync [
-  'clean'
   'gen_js'
   'gen_css'
-  'gen_markdown'
   'gen_html'
   'copy'
 ]
@@ -109,8 +76,8 @@
 gulp.task 'deploy', [], $.shell.task [ 'surge ' + config.prod_path ]

 gulp.task 'watcher', ->
-  gulp.watch path.join(config.dev_path_coffee, '*.coffee'), ['gen_js_live']
-  gulp.watch path.join(config.dev_path_sass, '*.sass'), ['gen_css_live']
-  gulp.watch path.join(config.dev_path, '*.html'), ['gen_html_live']
+  gulp.watch path.join(config.dev_path_coffee, '*.coffee'), ['gen_js']
+  gulp.watch path.join(config.dev_path_sass,   '*.sass'),   ['gen_css']
+  gulp.watch path.join(config.dev_path,        '*.html'),   ['gen_html']
   return


livereload based on file extension - will become configurable

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -39,7 +39,7 @@

 gulp.task "livereload", ->
   reload = livereload()
-  gulp.watch("harp/**").on "change", (file) ->
+  gulp.watch("harp/**/*.+(jade|css)").on "change", (file) ->
     reload.changed(file.path)

 gulp.task "default", ["server", "compass", "livereload"]


Reduced number of tests for TravisCI

Reduced number of tests for TravisCI

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -74,7 +74,7 @@
   Async.series [testNode, testBrowsers], (err) -> (require './test/lib/test_server').release(err)
   return # promises workaround: https://github.com/gulpjs/gulp/issues/455

-gulp.task 'test-quick', ['build', 'start-test-server'], testNode
+gulp.task 'test-quick', ['test'], testNode
 gulp.task 'test-node-quick', ['build', 'start-test-server'], testNode
 gulp.task 'test-browsers-quick', ['build', 'start-test-server'], testBrowsers


modifying gulpfile.

modifying gulpfile.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -47,22 +47,6 @@
   .pipe uglify()
   .pipe gulp.dest "contents/js"
   .pipe livereload()
-
-rsync_ssh = ->
-  knownOpts =
-    string: 'dest'
-    defaults: ""
-  opts = if process.argv.length > 1 then minimist process.argv.slice(2), knownOpts else {'dest':null}
-  if opts.dest
-    rsync
-      ssh: true
-      src: 'build/'
-      dest: opts.dest
-      recursive: true
-      (error,stdout,stderr,cmd)->
-        if error
-          gutil.log error.message
-        gutil.log stdout

 gulp.task "newPost", ->
   date = new Date()
@@ -123,7 +107,20 @@
   gutil.log gutil.colors.white.bgBlue("Build..."), "Complete"

 gulp.task "push", ->
-  rsync_ssh()
+  knownOpts =
+    string: 'dest'
+    defaults: ""
+  opts = if process.argv.length > 1 then minimist process.argv.slice(2), knownOpts else {'dest':null}
+  if opts.dest
+    rsync
+      ssh: true
+      src: 'build/'
+      dest: opts.dest
+      recursive: true
+      (error,stdout,stderr,cmd)->
+        if error
+          gutil.log error.message
+        gutil.log stdout

 # watch tasks watch folders and call functions defined above on change
 gulp.task 'default', ['css', 'js', 'preview'], ->


Fix jad vs pug

Fix jad vs pug

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -15,7 +15,7 @@

 sourcemaps = require 'gulp-sourcemaps'

-jade = require 'jade'
+jade = require 'pug'
 del = require 'del'

 source =


fix(#14): open app only when using backendless

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -462,7 +462,7 @@
 		.on 'error', onError

 # Default build
-gulp.task 'default', [].concat(if runServer then ['open'] else ['build']).concat(if runWatch then ['watch'] else []).concat(if runSpecs then ['test'] else [])
+gulp.task 'default', [].concat(if runServer and useBackendless then ['open'] else ['build']).concat(if runWatch then ['watch'] else []).concat(if runSpecs then ['test'] else [])

 # Execute E2E tests
 gulp.task 'e2e', ->
@@ -1278,4 +1278,4 @@

 	gulp
 		.watch sources, {cwd: SRC_DIRECTORY, maxListeners: 999}, tasks
-		.on 'error', onError+		.on 'error', onError


use relative paths in source maps instead of absolute

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -57,7 +57,7 @@
     b = b.pipe source target
     b = b.pipe buffer()
     b = b.pipe transform(->
-      exorcist conf.dist_dir + target + '.map', null, '../') if !minify
+      exorcist conf.dist_dir + target + '.map', null, '../', './') if !minify
     b = b.pipe uglify() if minify
     b = b.pipe gulp.dest conf.dist_dir


Improve error handling

Improve error handling

Handle errors earlier for the `coffee` task and print errors to stderr.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -21,7 +21,7 @@
   ]

 printError = (err) ->
-  console.log err
+  console.error err
   @emit 'end'

 gulp.task 'coffee', ->
@@ -32,9 +32,9 @@
   )

   b.bundle()
+    .on('error', printError)
     .pipe(source paths.app)
     .pipe(buffer())
-    .on('error', printError)
     .pipe(sourcemaps.init loadMaps: true)
       .pipe(uglify())
       .pipe(rename extname: '.min.js')


Gulpfile: Use play by default, update the watchers and add watch-playground task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -100,21 +100,19 @@
     gutil.log gutil.colors.magenta "file #{event.path} was #{event.type}"

-gulp.task 'live', -> useLiveReload = yes
+gulp.task 'watch-playground', ->

-
-gulp.task 'watch-html', ->
-
-  watcher = gulp.watch ['./src/**/*.html'], ['html']
+  watcher = gulp.watch [
+    './playground/**/*.coffee'
+    './playground/**/*.html'
+  ], ['play']

   watcher.on 'change', (event)->
     gutil.log gutil.colors.blue "file #{event.path} was #{event.type}"

-gulp.task 'html', ->
 gulp.task 'play', ->

-  try fs.mkdirSync './build'
   stream = gulp.src(ENTRY_PATH, { read: false })
     .pipe(browserify
       transform   : ['coffeeify']
@@ -123,19 +121,20 @@
     .pipe(concat "main.js")
     .pipe(gulp.dest "playground/js")

-  fs.writeFileSync './build/index.html', fs.readFileSync './src/index.html'
   stream.pipe(livereload())  if useLiveReload
-
-  gulp.src('./src/index.html').pipe(livereload())  if useLiveReload

   if useLiveReload
     gulp.src('./playground/index.html').pipe(livereload())

-gulp.task 'default', ['live', 'styles', 'libs', 'coffee', 'html', \
-                      'watch-html', 'watch-styles', 'watch-coffee', 'watch-libs'], ->
+gulp.task 'live', ->

-  http.createServer(ecstatic {root: "#{__dirname}/build"}).listen(8080)
-  gutil.log gutil.colors.blue 'HTTP server ready localhost:8080'
+  useLiveReload = yes

 gulp.task 'compile', ['styles', 'libs', 'coffee']
+
+gulp.task 'default', ['live', 'compile', 'play', 'watch-styles',  \
+                      'watch-coffee', 'watch-libs', 'watch-playground'] , ->
+
+  http.createServer(ecstatic {root: "#{__dirname}/playground"}).listen(8080)
+  gutil.log gutil.colors.blue 'HTTP server ready localhost:8080'


prevent double calls to rename

prevent double calls to rename

--- services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee
+++ services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee
@@ -28,6 +28,9 @@

 		invalidModalShowing = false
 		$scope.finishRenaming = () ->
+			# avoid double events when blur and on-enter fire together
+			return if !$scope.entity.renaming
+
 			name = $scope.inputs.name

 			if !name.match(new RegExp(ide.validFileRegex))


Fix spacing when too many buttons

Fix spacing when too many buttons

--- myapp/client/panels.coffee
+++ myapp/client/panels.coffee
@@ -11,13 +11,19 @@
 		@notifier = null

 		p = 0.1
+		px = 0
+		total = 0
 		@keys = _.keys(@choices)
 		@keys = _.shuffle(@keys)
 		_.forEach(@keys, (key) ->
+			if (total == 4)
+				px += 170
+				p = 0.1
+				total -= 4
 			data = self.choices[key]
 			button = Widgets.createButton({
       			text: data.text,
-      			x: 30,
+      			x: px + 30,
       			y: self.layer.getHeight()*p,
       			width: 100
 			})
@@ -28,7 +34,7 @@
 			self.layer.add button

 			dot = new Kinetic.Circle(
-				x: 15
+				x: px + 15
 				y: self.layer.height()*p + button.height()/2
 				radius: 5
 				stroke: 'black'
@@ -36,7 +42,8 @@
 			data.dot = dot
 			self.layer.add dot

-			p += 0.8/_.size(self.choices)
+			p += 0.8/_.min([_.size(self.choices), 4])
+			total++
 		)

 	setNotifier: (@notifier) ->


shortcut for atom-notes

shortcut for atom-notes

--- keymap.cson
+++ keymap.cson
@@ -33,3 +33,6 @@
   'alt-m ctrl-b': 'atom-keyboard-macros:execute_macro_from_top_to_bottom'
   'alt-m ctrl-e': 'atom-keyboard-macros:repeat_last_kbd_macro'
   'alt-m e': 'atom-keyboard-macros:call_last_kbd_macro'
+
+'atom-text-editor':
+  'ctrl-alt-n': 'atom-notes:toggle'


added spec sprite to sprites without sprite source (to fix sprite-less situations)

--- ng-modules/Location.coffee
+++ ng-modules/Location.coffee
@@ -36,4 +36,7 @@
                     "station1", -1000, 'random')
             when "maneuver"
                 return new Sprite('assets/sprites/maneuver-node/sprites.png',
-                    "maneuver-node", -1000, 300);+                    "maneuver-node", -1000, 300);
+
+            else
+                return new Sprite('assets/sprites/spec.png', {w:3, h:3}, 0, "random");


fixing PR #251

fixing PR #251

--- src/smc-webapp/buttonbar.coffee
+++ src/smc-webapp/buttonbar.coffee
@@ -522,33 +522,37 @@
             insert : "[1, 2, 5, 6, 10]"
         list_comprehension :
             insert : "[n+1 for n in range(10) if n%2==0]"
-        read_csv : """
-                        import csv
-                        import sys
-
-                        f = open('example.csv', 'rt')
-                        try:
-                            reader = csv.reader(f)
-                            for row in reader:
-                                print row
-                        finally:
-                            f.close()
-                        """
-        write_csv : """
-                        import csv
-                        import sys
-
-                        f = open('example.csv', 'wt')
-                        try:
-                            writer = csv.writer(f)
-                            writer.writerow( ('Title 1', 'Title 2', 'Title 3') )
-                            for i in range(10):
-                                writer.writerow( (i+1, chr(ord('a') + i), '08/%02d/07' % (i+1)) )
-                        finally:
-                            f.close()
-
-                        print open('example.csv', 'rt').read()
-                        """
+        read_csv_file :
+                    insert:
+                         """
+                         import csv
+                         import sys
+
+                         f = open('example.csv', 'rt')
+                         try:
+                             reader = csv.reader(f)
+                             for row in reader:
+                                 print row
+                         finally:
+                             f.close()
+                         """
+        write_csv_file :
+                    insert:
+                         """
+                         import csv
+                         import sys
+
+                         f = open('example.csv', 'wt')
+                         try:
+                             writer = csv.writer(f)
+                             writer.writerow( ('Title 1', 'Title 2', 'Title 3') )
+                             for i in range(10):
+                                 writer.writerow( (i+1, chr(ord('a') + i), '08/%02d/07' % (i+1)) )
+                         finally:
+                             f.close()
+
+                         print open('example.csv', 'rt').read()
+                         """
         dict :
             insert : "{'sage':'math', 3:7}"
         set :
@@ -1368,8 +1372,7 @@
     add_icon(pybar, "<i class='fa'>#</i>", "#comment", "Comment selected text")

     py_control = ["Data", "Basic Data Types",
-           [
-            ["Construction"],
+           [["Construction"],
             ["Dictionary", "#dict"],
             ["List", "#list"],
             ["List Comprehension", "#list_comprehension"],


Don't override the default options!

Don't override the default options!

--- src/space-pen-extensions.coffee
+++ src/space-pen-extensions.coffee
@@ -74,7 +74,7 @@

   tooltipOptions.title = "#{tooltipOptions.title} #{getKeystroke(bindings)}"

-  this.tooltip(jQuery.extend(tooltipDefaults, tooltipOptions))
+  this.tooltip(jQuery.extend({}, tooltipDefaults, tooltipOptions))

 jQuery.fn.setTooltip.getKeystroke = getKeystroke
 jQuery.fn.setTooltip.replaceModifiers = replaceModifiers


icon: allow ripple disable

icon: allow ripple disable

--- icon/index.coffee
+++ icon/index.coffee
@@ -8,9 +8,10 @@
   constructor: ->
     styles.use()

-  render: ({icon, isDark, isInactive}) ->
+  render: ({icon, isDark, isInactive, shouldRipple}) ->
     icon ?= 'cake-variant'
     isDark ?= false
+    shouldRipple ?= true

     rippleColor = switch
       when isDark
@@ -25,11 +26,12 @@
           isInactive
         }
         onmousedown: z.ev (e, $$el) ->
-          RipplerService.ripple {
-            $$el
-            color: rippleColor
-            isSmall: true
-          }
+          if shouldRipple
+            RipplerService.ripple {
+              $$el
+              color: rippleColor
+              isSmall: true
+            }
       },
       z '.icon',
         className: icon


order sprints

order sprints

--- src/sprint/models/sprint.coffee
+++ src/sprint/models/sprint.coffee
@@ -18,13 +18,14 @@
         console.warn err

     @getByProjectId = (projectId) ->
-      @query
+      @query(
         where:
           project:
             __type: "Pointer"
             className: "Project"
             objectId: projectId
-
+      ).then (sprints) ->
+        _.sortByOrder sprints, 'number', false
     @close = (sprint) ->
       sprint.isActive = false
       sprint.save()


Move propTypes to top

Move propTypes to top

--- src/components/buttons/async-button.cjsx
+++ src/components/buttons/async-button.cjsx
@@ -6,6 +6,16 @@

 module.exports = React.createClass
   displayName: 'AsyncButton'
+
+  propTypes:
+    isWaiting: React.PropTypes.bool.isRequired
+    isDone: React.PropTypes.bool
+    isFailed: React.PropTypes.bool
+    waitingText: React.PropTypes.node # TODO: This should be a Component or array
+    failedState: React.PropTypes.func
+    failedProps: React.PropTypes.object
+    doneText: React.PropTypes.node
+    isJob: React.PropTypes.bool

   getInitialState: ->
     isTimedout: false
@@ -24,16 +34,6 @@
   checkForTimeout: ->
     {isWaiting} = @props
     @setState(isTimedout: true) if isWaiting
-
-  propTypes:
-    isWaiting: React.PropTypes.bool.isRequired
-    isDone: React.PropTypes.bool
-    isFailed: React.PropTypes.bool
-    waitingText: React.PropTypes.node # TODO: This should be a Component or array
-    failedState: React.PropTypes.func
-    failedProps: React.PropTypes.object
-    doneText: React.PropTypes.node
-    isJob: React.PropTypes.bool

   getDefaultProps: ->
     isDone: false


Add diagnosis date.

Add diagnosis date.

--- src/subject/subject.data.coffee
+++ src/subject/subject.data.coffee
@@ -14,6 +14,11 @@
     date = DateHelper.asMoment(subject.birthDate)
     # Anonymize the birth date.
     subject.birthDate = DateHelper.anonymize(date)
+
+  # Fix the diagnosis date.
+  if subject.diagnosisDate?
+    date = DateHelper.asMoment(subject.diagnosisDate)
+    subject.diagnosisDate = date

   # Fix the encounter dates.
   for enc in subject.encounters


added unit test for zero values

added unit test for zero values

--- src/test/tests/testCache.coffee
+++ src/test/tests/testCache.coffee
@@ -74,6 +74,26 @@
       }, (err, result) ->
         expect(err).to.be.null
         expect(result).to.deep.equal({})
+        done()
+
+  describe.only "zero checker", ->
+    zeroDriver = (request, callback) ->
+      callback(null, { prop: { Count: 0 } })
+      return
+
+    zeroDriverCached = driverCache({
+      driver: zeroDriver
+    })
+
+    it "should handle zeroes", (done) ->
+      zeroDriverCached {
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'apply', name: 'Count', aggregate: 'constant', value: '0' }
+        ]
+      }, (err, result) ->
+        expect(err).to.be.null
+        expect(result).to.deep.equal({ prop: { Count: 0 } })
         done()

   describe "(sanity check) apply count", ->


Make sure to sync following genes.

Make sure to sync following genes.

--- apps/gene/client/index.coffee
+++ apps/gene/client/index.coffee
@@ -17,6 +17,7 @@
       el: $('.follow-button')
       following: if @user then new Following(null, kind: 'gene') else null
       model: @model
+    @followButton.following?.syncFollows [@model.get('id')]
     @shareButtons = new ShareView
       el: @$('#gene-share-buttons')
     @filter = new GeneFilter


add some option/alt keyboard shortcuts for touch devices with keyboard...

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -809,17 +809,16 @@

             "Ctrl-L"       : (editor)   => @goto_line(editor)
             "Cmd-L"        : (editor)   => @goto_line(editor)
-            "Alt-L"        : (editor)   => @goto_line(editor)

             "Ctrl-I"       : (editor)   => @toggle_split_view(editor)
             "Cmd-I"        : (editor)   => @toggle_split_view(editor)

             "Shift-Cmd-L"  : (editor)   => editor.align_assignments()
             "Shift-Ctrl-L" : (editor)   => editor.align_assignments()
-            "Shift-Alt-L" : (editor)   => editor.align_assignments()

             "Shift-Ctrl-." : (editor)   => @change_font_size(editor, +1)
             "Shift-Ctrl-," : (editor)   => @change_font_size(editor, -1)
+
             "Shift-Cmd-."  : (editor)   => @change_font_size(editor, +1)
             "Shift-Cmd-,"  : (editor)   => @change_font_size(editor, -1)

@@ -836,16 +835,40 @@

             "Ctrl-Space"   : "autocomplete"

-            #"F11"          : (editor)   => console.log('fs', editor.getOption("fullScreen")); editor.setOption("fullScreen", not editor.getOption("fullScreen"))
-
-        if opts.bindings == 'vim'
-            # An additional key to get to visual mode in vim (added for ipad Smart Keyboard)
-            extraKeys["Alt-C"] = (editor) =>
-                CodeMirror.Vim.exitInsertMode(editor)
-            extraKeys["Alt-F"] = (editor) =>
-                editor.execCommand('goPageDown')
-            extraKeys["Alt-B"] = (editor) =>
-                editor.execCommand('goPageUp')
+        if feature.IS_TOUCH
+            # Better more external keyboard friendly shortcuts, motivated by iPad.
+            misc.merge extraKeys,
+                "Alt-L"        : (editor)   => @goto_line(editor)
+                "Alt-I"        : (editor)   => @toggle_split_view(editor)
+                "Shift-Alt-L"  : (editor)   => editor.align_assignments()
+                'Alt-Z'        : (editor)   => editor.undo()
+                'Shift-Alt-Z'  : (editor)   => editor.redo()
+                'Alt-A'        : (editor)   => editor.execCommand('selectAll')
+                'Shift-Alt-A'  : (editor)   => editor.execCommand('selectAll')
+                'Alt-K'        : (editor)   => editor.execCommand('killLine')
+                'Alt-D'        : (editor)   => editor.execCommand('selectNextOccurrence')
+                'Alt-C'        : (editor)   => @copy(editor)  # gets overwritten for vim mode, of course
+                'Alt-X'        : (editor)   => @cut(editor)
+                'Alt-V'        : (editor)   => @paste(editor)
+                'Alt-F'        : (editor)   => editor.execCommand('find')
+                'Shift-Alt-F'  : (editor)   => editor.execCommand('replace')
+                'Shift-Alt-R'  : (editor)   => editor.execCommand('replaceAll')
+                'Shift-Alt-D'  : (editor)   => editor.execCommand('duplicateLine')
+                'Alt-G'        : (editor)   => editor.execCommand('findNext')
+                'Alt-Up'       : (editor)   => editor.execCommand('goPageUp')
+                'Alt-Down'     : (editor)   => editor.execCommand('goPageDown')
+                'Alt-P'        : (editor)   => editor.execCommand('goLineUp')
+                'Alt-N'        : (editor)   => editor.execCommand('goLineDown')
+                'q'  : (editor)   => console.log('hi'); editor.execCommand('findPrevious')
+
+            if opts.bindings == 'vim'
+                # An additional key to get to visual mode in vim (added for ipad Smart Keyboard)
+                extraKeys["Alt-C"] = (editor) =>
+                    CodeMirror.Vim.exitInsertMode(editor)
+                extraKeys["Alt-F"] = (editor) =>
+                    editor.execCommand('goPageDown')
+                extraKeys["Alt-B"] = (editor) =>
+                    editor.execCommand('goPageUp')

         if opts.match_xml_tags
             extraKeys['Ctrl-J'] = "toMatchingTag"
@@ -916,6 +939,7 @@

             if opts.code_folding
                 extraKeys["Ctrl-Q"] = (cm) -> cm.foldCodeSelectionAware()
+                extraKeys["Alt-Q"]  = (cm) -> cm.foldCodeSelectionAware()
                 options.foldGutter  = true
                 options.gutters     = ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]

@@ -949,6 +973,7 @@

         @codemirror = make_editor(elt[0])
         @codemirror.name = '0'
+        #window.cm = @codemirror

         elt1 = @element.find(".webapp-editor-textarea-1")

@@ -1215,8 +1240,10 @@
                 @goto_line(cm)
             when 'copy'
                 @copy(cm)
+                cm.focus()
             when 'paste'
                 @paste(cm)
+                cm.focus()
             when 'sagews2pdf'
                 @print(sagews2html = false)
             when 'print'
@@ -1342,6 +1369,12 @@
         if not cm?
             return
         copypaste.set_buffer(cm.getSelection())
+
+    cut: (cm) =>
+        if not cm?
+            return
+        copypaste.set_buffer(cm.getSelection())
+        cm.replaceSelection('')

     paste: (cm) =>
         if not cm?


Simplify parentAttribute handling

Simplify parentAttribute handling

--- src/trix/views/text_view.coffee
+++ src/trix/views/text_view.coffee
@@ -13,7 +13,9 @@
     @text.eachPieceWithPosition (piece, position) =>
       [@previousPiece, @previousAttributes] = [@currentPiece, @currentAttributes]
       [@currentPiece, @currentAttributes] = [piece, piece.getAttributes()]
-      @parentAttribute = @findParentAttribute()
+
+      if parentAttribute = @findParentAttribute()
+        delete @currentAttributes[parentAttribute]

       element = @createElementForCurrentPieceWithPosition(position)
       pieceView = @createChildView(Trix.PieceView, piece, position)
@@ -24,7 +26,7 @@
         before = @createCursorTargetForPosition(position)
         after = @createCursorTargetForPosition(position + 1)

-      if @parentAttribute
+      if parentAttribute
         @element.insertBefore(left, @element.lastChild) if left?
         @element.lastChild.appendChild(element)
         @element.appendChild(right) if right?
@@ -49,15 +51,14 @@
         styles.push(config.style)

       if config.tagName
-        unless config.parent and key is @parentAttribute
-          element = document.createElement(config.tagName)
-          element.setAttribute(key, value) unless typeof(value) is "boolean"
-          @recordNode(element, offset: position)
+        element = document.createElement(config.tagName)
+        element.setAttribute(key, value) unless typeof(value) is "boolean"
+        @recordNode(element, offset: position)

-          if config.parent
-            elements.unshift(element)
-          else
-            elements.push(element)
+        if config.parent
+          elements.unshift(element)
+        else
+          elements.push(element)

     if styles.length
       unless elements.length


Follow up 92b7c398797deec96a3828e4613c1ed2448c0889 - Fixes #3930 - Make sidebar attachments unique.

--- app/assets/javascripts/app/controllers/ticket_zoom/sidebar_article_attachments.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom/sidebar_article_attachments.coffee
@@ -1,4 +1,12 @@
 class SidebarArticleAttachments extends App.Controller
+  constructor: ->
+    super
+    @controllerBind('ui::ticket::load', (data) =>
+      return if data.ticket_id.toString() isnt @ticket.id.toString()
+      return if !@el
+      @showObjects(@el)
+    )
+
   sidebarItem: =>
     return if !@Config.get('ui_ticket_zoom_sidebar_article_attachments')
     @item = {
@@ -43,9 +51,6 @@
     @el.on('click', '.js-attachments img', (e) =>
       @imageView(e)
     )
-    @controllerBind('ui::ticket::load', =>
-      @showObjects(el)
-    )

   imageView: (e) ->
     e.preventDefault()


data.permissions is deprecated. config.permissions is used now

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -37,7 +37,7 @@

             @_status = []

-            @_hiddenKeys.push 'data', 'parent', 'children', 'lookup', 'configure', 'init', 'boot', 'booting', 'booted', 'start', 'started', 'running', 'shutdown', 'stop', 'stopped', 'error', 'delegate'
+            @_hiddenKeys.push 'data', 'parent', 'children', 'permissions', 'lookup', 'configure', 'init', 'boot', 'booting', 'booted', 'start', 'started', 'running', 'shutdown', 'stop', 'stopped', 'error', 'delegate'

         ##
@@ -50,16 +50,22 @@
             @id = config.id

             @ID = if !(@parent?.ID) then @id else @parent.ID+'.'+@id
-
-            @data = new floyd.data.SearchableMap config.data, @parent?.data

             if typeof (@type = config.type) is 'function'
                 @type = @ID+'.'+(@type.name || 'DynContext')

+            @data = new floyd.data.SearchableMap config.data, @parent?.data
+
+            @logger = @_createLogger @ID
+
+            if @data.permissions
+                @logger.warning '@data.permissions is deprecated use config.permissions instead'
+                config.permissions = @data.permissions
+
+            @permissions = config.permissions
+
             @identity = @_createIdentity()
-
-            @logger = @_createLogger @ID
-
+
             @_emitter = @_createEmitter config

             # --> EXPERIMENTAL identity based lookups cache -> nero
@@ -278,8 +284,8 @@

                 checks = [
                     (next)=>
-                        if @data.permissions?.__checks
-                            for check in @data.permissions.__checks
+                        if @permissions?.__checks
+                            for check in @permissions.__checks
                                 do(check)=>
                                     checks.push (next)=>
                                         check.apply @, [identity, key, args, next]
@@ -291,13 +297,13 @@

                     (next)=> # check for general remote restriction

-                        next (@data.permissions?[key] || @data.permissions) isnt false
+                        next (@permissions?[key] || @permissions) isnt false

                 ,

                     (next)=> # check for login restriction

-                        if (@data.permissions?[key]?.login || @data.permissions?.login)
+                        if (@permissions?[key]?.login || @permissions?.login)

                             identity.login (err, login)=>
                                 next !!login
@@ -307,7 +313,7 @@

                      (next)=> # check for user restriction and test identity.login

-                        if user = (@data.permissions?[key]?.user || @data.permissions?.user)
+                        if user = (@permissions?[key]?.user || @permissions?.user)

                             identity.login (err, login)=>
                                 next login is user
@@ -316,7 +322,7 @@
                 ,

                     (next)=> # check for roles restriction and test identity.hasRole
-                        if roles = (@data.permissions?[key]?.roles || @data.permissions?.roles)
+                        if roles = (@permissions?[key]?.roles || @permissions?.roles)

                             identity.hasRole roles, (err, ok)=>
                                 next ok
@@ -326,7 +332,7 @@
                 ,

                     (next)=> # custom check function - must callback true to permit!
-                        if typeof (check = @data.permissions) is 'function' || typeof (check = @data.permissions?[key]) is 'function' || check = (@data.permissions?[key]?.check || @data.permissions?.check)
+                        if typeof (check = @permissions) is 'function' || typeof (check = @permissions?[key]) is 'function' || check = (@permissions?[key]?.check || @permissions?.check)

                             check identity, key, args, next


JSListView update

JSListView update

--- JSKit/04_JSListView.coffee
+++ JSKit/04_JSListView.coffee
@@ -30,7 +30,9 @@
 			$(@_viewSelector+"_select").remove()
 		$(@_viewSelector).append(@_tag)

-		$(@_viewSelector+"_select").change =>
+		$(@_viewSelector+"_select").click (e) =>
+			e.stopPropagation()
+		$(@_viewSelector+"_select").change (e) =>
 			select = $(@_viewSelector+"_select option:selected").val()
 			if (@_action?)
 				@_action(select)


Add support for the popular "hbs" file extension

--- config/files.coffee
+++ config/files.coffee
@@ -48,7 +48,7 @@
     minifiedWebRelative: "css/app.css"

   template:
-    handlebars: ["app/templates/**/*.handlebar", "app/templates/**/*.handlebars", "app/templates/**/*.hb"]
+    handlebars: "app/templates/**/*.{hb,hbs,handlebar,handlebars}"
     underscore: ["app/templates/**/*.underscore", "app/templates/**/*.us"]
     generatedHandlebars: "generated/template/handlebars.js"
     generatedUnderscore: "generated/template/underscore.js"


ivey's request

ivey's request

--- scripts/favorite-images.coffee
+++ scripts/favorite-images.coffee
@@ -6,3 +6,6 @@
 module.exports = (robot) ->
   robot.respond /show me dancing cat/i, (msg) ->
     msg.send "https://d1ij7zv8zivhs3.cloudfront.net/assets/2719117/original/animated-dancing%20cat.gif"
+
+  robot.respond /show me how many fucks i give/i, (msg) ->
+    msg.send "http://mlkshk.com/r/AGC6.gif"


Refactor dashboard tests inline with other tests

--- server/test/integration/dashboard_index.coffee
+++ server/test/integration/dashboard_index.coffee
@@ -47,17 +47,15 @@
   return promises

-suite('Page load')
+suite('Dashboard')

-test('get dashboard url', (done) ->
+test('GET index', (done) ->
   request.get appurl('/dashboard'), (err, res, body) ->
     assert.equal 200, res.statusCode
     done()
 )

-suite('Reports section')
-
-test('has latest edited reports listed', (done) ->
+test('GET index has latest edited reports listed', (done) ->
   createReportModels([
     {
       title: 'Report 1'


fix #343

fix #343

Enable Angular grammer by default

--- keymaps/emmet.cson
+++ keymaps/emmet.cson
@@ -33,7 +33,7 @@

 # language-specific Tab triggers
 # you can add more triggers by changing `grammar` attribute values
-'atom-text-editor[data-grammar="text html basic"]:not([mini]), atom-text-editor[data-grammar~="erb"]:not([mini]), atom-text-editor[data-grammar~="jade"]:not([mini]), atom-text-editor[data-grammar~="css"]:not([mini]), atom-text-editor[data-grammar~="stylus"]:not([mini]), atom-text-editor[data-grammar~="sass"]:not([mini]), atom-text-editor[data-grammar~="scss"]:not([mini])':
+'atom-text-editor[data-grammar="text html basic"]:not([mini]), atom-text-editor[data-grammar~="angular"]:not([mini]), atom-text-editor[data-grammar~="erb"]:not([mini]), atom-text-editor[data-grammar~="jade"]:not([mini]), atom-text-editor[data-grammar~="css"]:not([mini]), atom-text-editor[data-grammar~="stylus"]:not([mini]), atom-text-editor[data-grammar~="sass"]:not([mini]), atom-text-editor[data-grammar~="scss"]:not([mini])':
   'tab': 'emmet:expand-abbreviation-with-tab'

 '.platform-darwin atom-text-editor:not([mini])':


Improve ctrl-delete binding for Win32

Improve ctrl-delete binding for Win32

--- keymaps/win32.cson
+++ keymaps/win32.cson
@@ -64,7 +64,7 @@
   'ctrl-shift-left': 'editor:select-to-beginning-of-word'
   'ctrl-shift-right': 'editor:select-to-end-of-word'
   'ctrl-backspace': 'editor:backspace-to-beginning-of-word'
-  'ctrl-delete': 'editor:backspace-to-beginning-of-word'
+  'ctrl-delete': 'editor:delete-to-end-of-word'
   'ctrl-home': 'core:move-to-top'
   'ctrl-end': 'core:move-to-bottom'


make sure the default language for a forum is english

--- @client/dashboard/forum_settings.coffee
+++ @client/dashboard/forum_settings.coffee
@@ -49,7 +49,7 @@
     subdomain = fetch '/subdomain'
     current_user = fetch '/current_user'

-    lang = @local.language or subdomain.lang
+    lang = @local.language or subdomain.lang or 'en'
     not_english = lang? && lang != 'en'

     return SPAN null if !subdomain.name


Generate random work, keep track of requests

--- lib/adapter.coffee
+++ lib/adapter.coffee
@@ -6,6 +6,7 @@
 class Adapter
   constructor: (options={}) ->
     {@busPorts, @queuePorts} = options
+    @pendingMessages = {}

   run: =>
     debug "Started adapter. Putting jobs in #{@queuePorts}, getting messages from #{@busPorts}"
@@ -23,15 +24,26 @@

     debug 'onMessage', id, messageStr
     _.each @buses, (bus) => bus.socket.unsubscribe id
+    @printMessage id, messageStr
+
+  printMessage: (id, message) =>
+    values = @pendingMessages[id].values
+    valuesStr = values.join ' + '
+    console.log "#{valuesStr} = #{message}"
+    delete @pendingMessages[id]

   sendJob: =>
     queue = _.sample @queues
     id    = uuid.v1()
-    debug "sendJob", "sending to: #{queue.port} with id: #{id}"
-    queue.socket.send JSON.stringify ['sum', [1, 1], id]
+    values = _.times _.random(1,10), => _.random(1,10)
+
+    @pendingMessages[id] = {operation: 'sum', values: values}

     debug "subscribing to #{id}"
     _.each @buses, (bus) => bus.socket.subscribe id
+
+    debug "sendJob", "sending to: #{queue.port} with id: #{id}"
+    queue.socket.send JSON.stringify ['sum', values, id]

   _createConnectedBus: (port) =>
     socket = zmq.socket 'sub'


status/table: Leave placeholder for pull to table but just display a message for now.

--- Oghma/status/table.coffee
+++ Oghma/status/table.coffee
@@ -34,7 +34,7 @@
       O.join_table( O.default_table )
       table.remove()
   pull_to = ( table ) ->
-    # XXX -- need mechanism for pulling all users to a table.
+    alert("Not yet implemented.")

   make_gm_only = ( table ) ->
     table.set( visible_to: [ O.GM ] )


Add Literal Haskell support

Add Literal Haskell support

--- lib/builder.coffee
+++ lib/builder.coffee
@@ -32,5 +32,5 @@
   resolveLogFilePath: (texFilePath) ->
     outputDirectory = atom.config.get('latex.outputDirectory') ? ''
     currentDirectory = path.dirname(texFilePath)
-    fileName = path.basename(texFilePath).replace(/\.tex$/, '.log')
+    fileName = path.basename(texFilePath).replace(/\.tex|\.lhs$/, '.log')
     logFilePath = path.join(currentDirectory, outputDirectory, fileName)


Added second delayed refresh after running request

--- SingularityUI/app/views/request.coffee
+++ SingularityUI/app/views/request.coffee
@@ -61,6 +61,9 @@
     runRequest: (e) =>
         @model.promptRun =>
             @trigger 'refreshrequest'
+            setTimeout =>
+                @trigger 'refreshrequest'
+            , 2500

     pauseRequest: (e) =>
         @model.promptPause =>


Update tonypai.weather.coffee

Update tonypai.weather.coffee

--- scripts/tonypai.weather.coffee
+++ scripts/tonypai.weather.coffee
@@ -4,12 +4,12 @@

     if result.match.length is 0
       city = "taichung"
-	  country = "tw"
+      country = "tw"
     else
       city = result.match[2].trim()
-	  country = result.match[3].trim()
+      country = result.match[3].trim()

-    url = "http://api.openweathermap.org/data/2.5/forecast/daily?q="+city+","+country
+    url = "http://api.openweathermap.org/data/2.5/forecast/daily?q="+city+"," + country

     robot.http(url)
       .header('Accept', 'application/json')


green buttons

green buttons

--- Modules/PhotoPicker/PhotoPickerTable.coffee
+++ Modules/PhotoPicker/PhotoPickerTable.coffee
@@ -21,10 +21,10 @@
     @addFromCameraButton = root.app.create("Button", {
       labelText: "Take Photo"
       icon: "Images/camera.png"
-      gradientFrom: ["#0d5acd", "#0b4eb3"]
-      gradientTo: ["#0b4eb3", "#0b4eb3"]
-      borderColor: "#0b4eb3"
-      labelShadowColor: "#0b4eb3"
+      gradientFrom: ["#08a700", "#068700"]
+      gradientTo: ["#068700", "#08a700"]
+      borderColor: "#067d00"
+      labelShadowColor: "#067d00"
       left: 5
       top: 5
       bottom: 5
@@ -36,10 +36,10 @@
     @addFromGalleryButton = root.app.create("Button", {
       labelText: "Choose From Library"
       icon: "Images/photos.png"
-      gradientFrom: ["#0d5acd", "#0b4eb3"]
-      gradientTo: ["#0b4eb3", "#0b4eb3"]
-      borderColor: "#0b4eb3"
-      labelShadowColor: "#0b4eb3"
+      gradientFrom: ["#08a700", "#068700"]
+      gradientTo: ["#068700", "#08a700"]
+      borderColor: "#067d00"
+      labelShadowColor: "#067d00"
       right: 5
       top: 5
       bottom: 5


.

Former-commit-id: 2f8230ef64fb5167c4b44f3f977fae0d0fd90489 [formerly 16fdecfa48580ea49122ad59c5b65fbbdcc45557] [formerly 90221da4b84e8d83176adb1542af1c129368da96 [formerly 7bad8565f63d221a1ae2f53f67697d201997cba7]] [formerly 730c0c261f82bb448d2ed1001b94c5bf63b0f0b4 [formerly 459963e4be21331d3a37d50a914ebd929967510e] [formerly 1739226a5209c1db31cd8dac1db863a059273221 [formerly 7149672ee00d78b126de5b0ee6b0256fab4c0db7]]]
Former-commit-id: 5189f2006503225aeda3fb3d75fc4744124b333c [formerly 6598be5c4cb3830eddae87cd8d7c1f1c03b485ac] [formerly 89a26c382b95ee501714d5fa9e958f8d52628a83 [formerly f69cdfabc8e1c65238a46a210f247486071673ce]]
Former-commit-id: 218ef07a5645b0a1e9990d144879e24fb6a32389 [formerly 4424d241bd08176841bebd5d07e902f3f0200ae1]
Former-commit-id: 19fe3009ab8bf24e34e2b780eb6f24da6436c5e5
Former-commit-id: 79fa6b9e758a4a392644bc00ea6e612a2311a6a6

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -598,7 +598,7 @@
   #show_proposer_icon: true
   proposal_filters: false
   opinion_filters: false
-
+
   "cluster/Bug Reports" :
     slider_pole_labels: relevance
     show_slider_feedback: false
@@ -623,7 +623,7 @@
   #   }]

-  "cluster/Hard tasks" :
+  "cluster/Hard Tasks" :
     slider_pole_labels: important_unimportant
     show_slider_feedback: false
     description:


Concat related article to schema

Concat related article to schema

--- api/apps/graphql/index.coffee
+++ api/apps/graphql/index.coffee
@@ -13,6 +13,9 @@

 app = module.exports = express()

+relatedArticles =
+  relatedArticles: array().items(object(Article.inputSchema))
+
 schema = joiql
   query:
     articles: array().items(object(
@@ -21,7 +24,7 @@
       args: Article.querySchema
       resolve: resolvers.articles
     )
-    article: object(Article.inputSchema)
+    article: object(Article.inputSchema).concat(object(relatedArticles))
     .meta(
       args: id: string()
       resolve: resolvers.article
@@ -51,8 +54,6 @@
       resolve: resolvers.authors
     )

-module.exports.schema = schema
-
 app.use '/graphql', setUser, graphqlHTTP(
   schema: schema
   graphiql: true


subtract window.scrollY from e.pageY to fix cropme in a modal frame

--- scripts/directives/cropme.coffee
+++ scripts/directives/cropme.coffee
@@ -253,7 +253,7 @@
 			deferred.promise

 		scope.mousemove = (e) ->
-			scope.colResizePointer = switch isNearBorders({x: e.pageX, y:e.pageY})
+			scope.colResizePointer = switch isNearBorders({x: e.pageX, y:(e.pageY - window.scrollY)})
 				when 'top' then 'ne-resize'
 				when 'right', 'bottom' then 'se-resize'
 				when 'left' then 'sw-resize'


Start the carousel on load

Start the carousel on load

--- scripts/views/home/layout.coffee
+++ scripts/views/home/layout.coffee
@@ -24,6 +24,8 @@

         @$el.html(template)

+        $('.carousel').carousel()
+
         #@regions.splash.show(new SplashView())
         #@regions.find.show(new FindView())
         #@regions.featured.show(new FeaturedView())


Fix typo

Fix typo

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -239,7 +239,7 @@
 				catch e
 					grunt.log.error "FAIL."
 					grunt.log.errorlns """
-					Please configure you Amazon S3 credentials in config/settings.development.coffee
+					Please configure your Amazon S3 credentials in config/settings.development.coffee

 					Amazon S3 (Simple Storage Service) is a cloud storage service provided by
 					Amazon. ShareLaTeX uses S3 for storing binary files like images. You can


v1.0.0

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -110,7 +110,7 @@
         bumpup:
             file: 'package.json'

-        clean: ['password-turtle.app', 'password-turtle.tgz', 'style/*.css', 'js', 'pepper', '.release.*']
+        clean: ['password-turtle.app', 'password-turtle.zip', 'style/*.css', 'js', 'pepper', '.release.*']

         githubAsset:


fix(grunt): Less sourcemaps not recognized

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -123,8 +123,8 @@
       serve:
         options:
           sourceMap: true
+          sourceMapFileInline: true
           outputSourceFiles: true
-          dumpLineNumbers: "comments"

         files: [
           expand: true
@@ -142,7 +142,7 @@
         src: "<%= amsf.theme.assets %>/css/*.css"
         options:
           map:
-            inline: false
+            inline: true
           processors: [
             require("autoprefixer")(browsers: "last 1 versions")
           ]


Updated gruntfile description.

Updated gruntfile description.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -87,6 +87,6 @@

   grunt.registerTask "css", "Compile the stylus files to css", [ "stylus" ]

-  grunt.registerTask "js", "Compile coffeescript and create all download files", [ "coffee", "component_build", "copy", "concat" ]
+  grunt.registerTask "js", "Compile coffeescript", [ "coffee", "component_build", "copy", "concat" ]

-  grunt.registerTask "downloads", [ "js", "css", "uglify" ]
+  grunt.registerTask "downloads", "Compile all stylus and coffeescript files and generate the download files" [ "js", "css", "uglify" ]


Add mkdir before benchmark

Add mkdir before benchmark

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -9,10 +9,8 @@

     @initConfig
         benchmark:
-            options:
-                displayResults: true
             all:
-                src: ['benchmarks/*.js'],
+                src: ['benchmarks/*.js']

         coffee:
             all:
@@ -49,4 +47,4 @@
     @registerTask 'build', ['clean', 'coffee']
     @registerTask 'package', ['build', 'release']
     @registerTask 'test', ['build', 'mkdir', 'mochacli']
-    @registerTask 'bench', ['build', 'benchmark']
+    @registerTask 'bench', ['build', 'mkdir', 'benchmark']


updated tasks

updated tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,7 +6,6 @@
       components:
         files:
           "assets/components.html": "components.html"
-          "assets/components-min.html": "assets/components.html"
     sass:
       default:
         files: "assets/css/app.min.css": "sass/app.scss"
@@ -17,7 +16,17 @@
       sass:
         files: "sass/*.scss"
         tasks: ["sass"]
+    browserSync:
+      dev:
+        bsFiles:
+          src: [
+            "assets/css/app.min.css"
+            "assets/js/*.js"
+          ]
+        options:
+          proxy: "localhost:2368"

   # Tasks
   grunt.registerTask "build", ["vulcanize", "sass"]
-  grunt.registerTask "default", ["build"]+  grunt.registerTask "serve", ["browserSync"]
+  grunt.registerTask "default", ["build", "serve"]


Updat Gruntfile

Updat Gruntfile

- Fix cssmin task working directory
- Better concurrent task ouput
- Optimize concurrent order

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -51,7 +51,7 @@

       test:
         files:
-          src: ["<%= core.assets %>/a.less"]
+          src: ["<%= core.app %>/css/a.less"]

     watch:
       coffee:
@@ -69,23 +69,14 @@
           dumpLineNumbers: "all"

         files:
-          "<%= core.assets %>/a.css": ["<%= core.assets %>/a.less"]
+          "<%= core.app %>/css/a.css": ["<%= core.app %>/css/a.less"]

       dist:
         options:
           paths: ["<%= core.assets %>"]

         files:
-          "<%= core.assets %>/a.css": ["<%= core.assets %>/a.less"]
-
-    cssmin:
-      dist:
-        options:
-          banner: "<%= core.banner %>"
-          report: "gzip"
-
-        files:
-          "<%= core.assets %>/a.css": ["<%= core.assets %>/*.css"]
+          "<%= core.app %>/css/a.css": ["<%= core.app %>/css/a.less"]

     htmlmin:
       dist:
@@ -118,6 +109,15 @@
           dest: "<%= core.dist %>/"
         ]

+    cssmin:
+      dist:
+        options:
+          banner: "<%= core.banner %>"
+          report: "gzip"
+
+        files:
+          "<%= core.dist %>/css/a.css": ["<%= core.dist %>/css/*.css"]
+
     shell:
       options:
         stdout: true
@@ -138,14 +138,11 @@
         command: "s3cmd sync -rP --guess-mime-type --delete-removed --no-preserve --cf-invalidate --exclude '.DS_Store' <%= core.cfg.static_files %> <%= core.cfg.s3_bucket %>"

     concurrent:
-      options:
-        logConcurrentOutput: true
-
       server:
         tasks: ["shell:server", "watch"]

       dist:
-        tasks: ["cssmin", "htmlmin", "xmlmin"]
+        tasks: ["htmlmin", "xmlmin", "cssmin"]

     clean: [".tmp", "<%= core.dist %>/*"]


turn sauce on again

turn sauce on again

maybe it’ll work by next deploy :-/

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -408,8 +408,8 @@
     'inlinelint'
     'build'
     'coffee'
-    # 'connect'
-    # 'saucelabs-mocha'
+    'connect'
+    'saucelabs-mocha'
   ]
   @registerTask 'app', ['build', 'phonegap-build']
   @registerTask 'default', ['test']


style(Gruntfile): remove superfluous parentheses

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,6 +1,6 @@
 module.exports = (grunt) ->
   grunt.initConfig
-    pkg: grunt.file.readJSON('package.json')
+    pkg: grunt.file.readJSON 'package.json'

     coffee:
       glob_to_multiple:
@@ -29,10 +29,6 @@
           stderr: true
           failOnError: true

-  grunt.loadNpmTasks('grunt-contrib-coffee')
-  grunt.loadNpmTasks('grunt-shell')
-  grunt.loadNpmTasks('grunt-coffeelint')
-  grunt.loadTasks('tasks')
     watch:
       scripts:
         files: ['src/*.coffee'],
@@ -40,13 +36,18 @@
         options:
           spawn: false

-  grunt.loadNpmTasks('grunt-contrib-watch')
+  grunt.loadNpmTasks 'grunt-coffeelint'
+  grunt.loadNpmTasks 'grunt-contrib-coffee'
+  grunt.loadNpmTasks 'grunt-contrib-watch'
+  grunt.loadNpmTasks 'grunt-shell'
+  grunt.loadTasks 'tasks'

   grunt.registerTask 'clean', ->
-    grunt.file.delete('lib') if grunt.file.exists('lib')
-    grunt.file.delete('gen') if grunt.file.exists('gen')
+    grunt.file.delete 'lib' if grunt.file.exists 'lib'
+    grunt.file.delete 'gen' if grunt.file.exists 'gen'

-  grunt.registerTask('lint', ['coffeelint'])
-  grunt.registerTask('default', ['coffeelint', 'coffee'])
-  grunt.registerTask('test', ['default', 'lint', 'shell:test'])
-  grunt.registerTask('prepublish', ['clean', 'build-grammars', 'test'])
+  grunt.registerTask 'lint', ['coffeelint']
+  grunt.registerTask 'build', ['coffeelint', 'coffee']
+  grunt.registerTask 'default', ['build']
+  grunt.registerTask 'test', ['build', 'lint', 'shell:test']
+  grunt.registerTask 'prepublish', ['clean', 'build-grammars', 'test']


revert Sass

revert Sass

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,27 +15,9 @@
       }
     },

-    sass: {
-      options: {
-        sourcemap: 'none'
-      },
-
-      projectreactor: {
-        files: {
-          'src/main/resources/public/stylesheets/projectreactor.css': 'src/main/sass/projectreactor.scss',
-          'src/main/resources/public/stylesheets/docs.css': 'src/main/sass/docs.scss'
-        }
-      }
-    },
-
     watch: {
       options: {
         livereload: true
-      },
-
-      sass: {
-        files: ['src/main/sass/**'],
-        tasks: ['sass:projectreactor', 'shell:resources']
       },

       asciidoc: {
@@ -47,10 +29,9 @@
   }

   grunt.loadNpmTasks 'grunt-shell'
-  grunt.loadNpmTasks 'grunt-contrib-sass'
   grunt.loadNpmTasks 'grunt-contrib-watch'

-  grunt.registerTask 'assemble', ['sass:projectreactor', 'shell:asciidoc']
+  grunt.registerTask 'assemble', ['shell:asciidoc']

   grunt.registerTask 'build', ['assemble',
                                'shell:build']


Fixes to the build and dist process

Fixes to the build and dist process

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -68,6 +68,10 @@
       js:
         expand: true
         src: "js/**"
+        dest: "dist/"
+      php:
+        expand: true
+        src: "functions/**"
         dest: "dist/"
       bower:
         expand: true
@@ -144,9 +148,9 @@
   grunt.registerTask "build", [
     "newer:concat"
     "less"
-    "coffee"
+    "newer:coffee"
     "cssmin"
-    "uglify"
+    "newer:uglify"
   ]

   # Build and watch for changes
@@ -157,10 +161,12 @@

   # Build the distribuition folder
   grunt.registerTask "dist", [
+    "build"
     "newer:copy:html"
     "newer:copy:img"
     "newer:copy:css"
     "newer:copy:js"
+    "newer:copy:php"
     "newer:copy:bower"
     "newer:copy:bootstrapSP"
     "newer:copy:bootstrapTOT"


Force coveralls to prevent Travis failure.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -37,6 +37,7 @@

     coveralls:
       options:
+        force: true
         coverage_dir: 'test/coverage/'

     jshint:


Load data to project page successfully

Load data to project page successfully

--- js/inner.coffee
+++ js/inner.coffee
@@ -79,21 +79,22 @@

 class CircleViewModel extends Base
-
-  loadAlways: =>
+  constructor: ->
     @current_user = ko.observable()
     $.getJSON '/api/v1/me', (data) =>
       @current_user(new User data)

-  loadRoot: =>
-    @loadAlways()
+    @builds = ko.observableArray()
     @projects = ko.observableArray()
     @recent_builds = ko.observableArray()

+  loadRoot: =>
+    @projects.removeAll()
     $.getJSON '/api/v1/projects', (data) =>
       for d in data
         @projects.push(new Project d)

+    @recent_builds.removeAll()
     $.getJSON '/api/v1/recent-builds', (data) =>
       for d in data
         @recent_builds.push(new Build d)
@@ -101,11 +102,10 @@
     $('#main').html(HAML['dashboard']({}))

   loadProject: (username, project) =>
-    @loadAlways()
+    @builds.removeAll()
     projectName = "#{username}/#{project}"
-    @builds = ko.observableArray()
     $.getJSON "/api/v1/project/#{projectName}", (data) =>
-      for d in data.recentBuilds
+      for d in data
         @builds.push(new Build d)

     $('#main').html(HAML['project']({}))


1.包管理添加无网络提示。

1.包管理添加无网络提示。

--- lib/package/package-center.coffee
+++ lib/package/package-center.coffee
@@ -111,9 +111,11 @@
     fetchAllPackageFormServer:() ->
         Q.promise (resolve, reject, notify) =>
           r = request.get "#{PuzzleServer}/api/packages?access_token=#{PuzzleAccessToken}&sequence=create_date", (err, res, body) =>
-            reject err if err
-            data = JSON.parse(body)
-            resolve data.packages
+            if err
+              reject err
+            else
+              data = JSON.parse(body)
+              resolve data.packages

     #搜索完成时回调
     onSearchDone: (packages, keyword)->
@@ -181,19 +183,24 @@
         installPathEditor.setText destPath[0]

     installPackage: (path, name, repo, callback)->
-      console.log "Start pull package,Path:#{path}, PackageName:#{name}, gitRepo:#{repo}"
-      git({
-        path: "#{path}"
-        name: name
-        repo: repo
-        }).then (destPath)->
-          console.log 'package pull finished'
-          callback(destPath) if callback
-      .catch (error)->
-       callback(destPath) if callback
+
+
+      (require "../../utils/checkNetwork")("http", "http://www.baidu.com").then =>
+        console.log "Start pull package,Path:#{path}, PackageName:#{name}, gitRepo:#{repo}"
+        git({
+          path: "#{path}"
+          name: name
+          repo: repo
+          })
+      .then (destPath) =>
+        console.log 'package pull finished'
+        callback(destPath) if callback
+      .catch (error) =>
+        @showTip 'Install error, please check out your network.'
+        callback() if callback

     onPrevPage: ->
-      # console.log '当前页', @currentPage
+      return unless @serverPackages
       @currentPage-- if @currentPage > 0
       startIndex = @currentPage * 10
       endIndex = startIndex + 10
@@ -201,6 +208,7 @@
       @showPopularPackage @result

     onNextPage: ->
+      return unless @serverPackages
       @currentPage++  if @currentPage + 1 < @serverPackages.length / 10
       startIndex = @currentPage * 10
       endIndex = startIndex + 10


fixed bug where search-results would remain present if window was refocused

--- _assets/js/_search.coffee
+++ _assets/js/_search.coffee
@@ -58,12 +58,10 @@
   $('#search-posts').focusout ->
     if !isSelected and $('#search-posts').val().length > 0
       $('#search-results').css('display', 'none')
-    else
-      $('#search-results').css('display', '')
     return

   $('#search-posts').focusin ->
-    if $('#search-results').css('display') == 'none'
+    if $('#search-results').css('display') == 'none' and $('#search-posts').val().length > 0
       $('#search-results').css('display', '')
     return


search now works locally and on gh-pages

--- _assets/js/_search.coffee
+++ _assets/js/_search.coffee
@@ -1,13 +1,14 @@
 $ ->
-  baseUrl = "/open-source-club-website" # github repo name
-  u = window.location.host.indexOf("github.io") # u = site url
-  if ! u > -1
-    baseUrl = ''
+
+  $baseUrl = '/open-source-club-website'
+  $u = window.location.host.indexOf('github.io')
+  if $u == -1
+    $baseUrl = ''

   SimpleJekyllSearch
     searchInput: document.getElementById('search-posts')
     resultsContainer: document.getElementById('search-results')
-    json: baseUrl + '/search.json'
+    json: $baseUrl + '/search.json'
     searchResultTemplate: '
{title}
'
     noResultsText: '
No results found
'
     limit: 10


minor changes to the preflight

minor changes to the preflight

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -63,9 +63,11 @@
     # 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
+        assert previous = @remotes or Array()
+        qualify = try subject.remote.compile
         noRemote = "the subject is not remote"
+        noPrevious = "invalid previous remotes"
+        assert _.isArray(previous), noPrevious
         assert _.isFunction(qualify), noRemote
         @remotes = previous.concat subject

@@ -78,10 +80,10 @@
         context.inline -> `assert = chai.assert`
         context.inline -> `assert(logger = log)`
         context.inline -> try logger.enableAll()
-        remotes = @constructor.remotes or []
-        for remote in _.unique remotes
-            @inject context, remote
-        return next()
+        assert remotes = @constructor.remotes or []
+        assert uniques = _.unique remotes or Array()
+        @inject context, blob for blob in uniques
+        return next.call this, undefined

 # This abstract base class service is an extension of the Screenplay
 # family that does some further environment initialization and set


app: Add maps layer.

app: Add maps layer.

--- Oghma/app.coffee
+++ Oghma/app.coffee
@@ -63,7 +63,14 @@
   keymap: null

   # Order of layers
-  layer_order: [ 'avatars', 'dice', 'annotations', 'pings', 'controls' ]
+  layer_order: [
+    'maps',
+    'avatars',
+    'dice',
+    'annotations',
+    'pings',
+    'controls'
+  ]

   # Layers
   layer: {}
@@ -266,6 +273,13 @@
           menu: Oghma.Menu.debug( this )
         }
       ]
+    )
+    @on( 'post_login', ( me ) =>
+      if @isGM()
+        @toolbar_main.add(
+          text: 'Maps'
+          menu: Oghma.Menu.maps( this )
+        )
     )
     @viewport.add( @toolbar_main )


Update atom config

Update atom config

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -4,7 +4,7 @@
     invisibles: {}
     softWrapAtPreferredLineLength: true
     showInvisibles: true
-    fontSize: 18
+    fontSize: 17
     scrollPastEnd: true
     tabLength: 4
   core:
@@ -39,3 +39,6 @@
   "one-dark-ui": {}
   "markdown-preview-plus": {}
   emmet: {}
+  tidal:
+    ghciPath: "/usr/bin/ghci"
+  "git-log": {}


Allow one level of braces in most LaTeX commands


Add host log

Add host log

--- server/ssr/renderOnServer.coffee
+++ server/ssr/renderOnServer.coffee
@@ -103,6 +103,7 @@
 defaultLang = (req) ->
     cookies = new Cookies(req.headers.cookie)
     host = req.hostname
+    console.log "Host=", host
     cookie = cookies.get('lang')
     if cookie
         return cookie


seo: set title as a slice of post body for posts

--- servers/lib/crawler/index.coffee
+++ servers/lib/crawler/index.coffee
@@ -45,8 +45,10 @@

       createActivityContent models, activity, (err, content, activityContent)->
         return callback err  if err or not content
+
+        summary = activityContent.body.slice(0, 80)
         graphMeta =
-          title    : "Post on koding.com by #{activityContent.fullName}"
+          title    : "#{summary} | Koding Community"
           body     : "#{activityContent.body}"
           shareUrl : "#{uri.address}/Activity/Post/#{activityContent.slug}"
         fullPage = feed.putContentIntoFullPage content, "", graphMeta


Simplify load method

Simplify load method

--- js/outer.coffee
+++ js/outer.coffee
@@ -19,11 +19,8 @@

       load: (show) ->
         self = this
-        if show?
-          $.getScript "assets/views/outer/#{@name}/#{@name}.hamlc", ->
-            self.render()
-        else
-          $.getScript "assets/views/outer/#{@name}/#{@name}.hamlc"
+        $.getScript "assets/views/outer/#{@name}/#{@name}.hamlc", ->
+          self.render() if show?

       display: ->
         if HAML? and HAML[@name]?


avatar uploading fixes

avatar uploading fixes

Former-commit-id: d35e6cc59d27e9589173ac1702e40176da8702e1 [formerly 4cd79670842d6652c536dc8fac43a6160bf7c67c] [formerly 04dd0522017e375a46aa110b54610669131f0327 [formerly 8bd896fa2cbc0b5dac3aff1051981f26da9bf514]] [formerly 88927da645d7e9c0b402641942f8e701a917a020 [formerly 9db70efbbd987004f153e487ca9bb152175fb8da] [formerly 2ec45f846188f67af02f065edddb8c53488deda3 [formerly 2ec45f846188f67af02f065edddb8c53488deda3 [formerly 4375429b20c58f7e475bc1e7e03593d297249285]]]]
Former-commit-id: 107e983279f591e82b2e5fed9016f85703a2ebdd [formerly 25f46e7b9f77aea852fccadf5030f19b2048a4a7] [formerly 26f9c085f7fed19cfab5175fd5128f3a5efdf9ad [formerly c888642b42bb393c3f93d87fc217dcdb186629e3]]
Former-commit-id: 838c118b0b2886318644ca9c74ca004836a20b02 [formerly 597cb9a3b077f0b69515ef1e06951d354e509b43]
Former-commit-id: f6f58f2a382d16dc10e129e49632ac1ceaf6ca96
Former-commit-id: a1156228de8e2954e3ff3642add42d0e1dfc69c7

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -754,10 +754,12 @@
 ensureCurrentUserAvatar = (attempts = 0) ->

   $.getJSON '/user_avatar_hack', (response) =>
-    if response.b64_thumbnail
-      current_user = fetch '/current_user'
-      $('head').append("<style type=\"text/css\">#avatar-#{current_user.id} { background-image: url('#{response.b64_thumbnail}');}</style>")
-
+    current_user = fetch '/current_user'
+
+    if response?[0]?.b64_thumbnail && current_user.b64_thumbnail != response[0].b64_thumbnail
+      $('head').append("<style type=\"text/css\">#avatar-#{current_user.id} { background-image: url('#{response[0].b64_thumbnail}');}</style>")
+      current_user.b64_thumbnail = response[0].b64_thumbnail
+      save current_user
     else if attempts < 20
       # Ugly: wait a little while for offline avatar processing to complete, then refetch
       _.delay ->


Update upgrade cards for renamed ships. Thank god we have tests.

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -36,10 +36,10 @@
         "Tech": "Technik"
     sources: # needed?
         "Core": "Grundspiel"
-        "A-Wing Expansion Pack": "A-Wing Erweiterung"
-        "B-Wing Expansion Pack": "B-Wing Erweiterung"
-        "X-Wing Expansion Pack": "X-Wing Erweiterung"
-        "Y-Wing Expansion Pack": "Y-Wing Erweiterung"
+        "A-Wing Expansion Pack": "A-Flügler Erweiterung"
+        "B-Wing Expansion Pack": "B-Flügler Erweiterung"
+        "X-Wing Expansion Pack": "X-Flügler Erweiterung"
+        "Y-Wing Expansion Pack": "Y-Flügler Erweiterung"
         "Millennium Falcon Expansion Pack": "Millenium Falke Erweiterung"
         "HWK-290 Expansion Pack": "HWK-290 Erweiterung"
         "TIE Fighter Expansion Pack": "TIE-Jäger Erweiterung"
@@ -52,7 +52,7 @@
         "Rebel Transport Expansion Pack": "Rebellentransporter Erweiterung"
         "Z-95 Headhunter Expansion Pack": "Z-95-Kopfjäger Erweiterung"
         "TIE Defender Expansion Pack": "TIE-Jagdbomber Erweiterung"
-        "E-Wing Expansion Pack": "E-Wing Erweiterung"
+        "E-Wing Expansion Pack": "E-Flügler Erweiterung"
         "TIE Phantom Expansion Pack": "TIE-Phantom Erweiterung"
         "Tantive IV Expansion Pack": "Tantive IV Erweiterung"
         "Rebel Aces Expansion Pack": "Fliegerasse der Rebellenallianz Erweiterung"
@@ -65,7 +65,7 @@
         "Imperial Raider Expansion Pack": "Imperiale Sturm-Korvette Erweiterung"
         "Hound's Tooth Expansion Pack": "Reisszahn Erweiterung"
         "Kihraxz Fighter Expansion Pack": "Kihraxz-Jäger Erweiterung"
-        "K-Wing Expansion Pack": "K-Wing Erweiterung"
+        "K-Wing Expansion Pack": "K-Flügler Erweiterung"
         "TIE Punisher Expansion Pack": "TIE-Vergelter Erweiterung"
         "The Force Awakens Core Set": "Das Erwachen der Macht Grundspiel"
         "Imperial Assault Carrier Expansion Pack": "Imperialer Angriffsträger Erweiterung"
@@ -1287,7 +1287,8 @@
             text: """Behandle in der Aktivierungsphase deinen Pilotenwert als "0"."""
         "Chardaan Refit":
             name: "Chardaan-Nachrüstung"
-            text: """Nur für A-Wing%LINEBREAK%Diese Karte hat negative Kommandopunktekosten."""
+            text: """Nur für A-Flügler%LINEBREAK%Diese Karte hat negative Kommandopunktekosten."""
+            ship: "A-Flügler"
         "Proton Rockets":
             name: "Protonenraketen"
             text: """Angriff (Fokussierung): Lege diese Karte ab, um mit dieser Sekundärwaffe anzugreifen.

Du darfst so viele zusätzliche Angriffswürfel werfen, wie du Wendigkeit hast (maximal 3 zusätzliche Würfel)."""
@@ -1411,7 +1412,8 @@
             text: """Sobald du eine offene Schadenskarte mit dem Attribut Schiff erhältst, darfst du sie sofort ablegen (bevor ihr Effekt abgehandelt wird).%LINEBREAK%Danach wird diese Aufwertungskarte abgelegt."""
         "Bomb Loadout":
             name: "Bombenladung"
-            text: """Nur für Y-Wing.%LINEBREAK%Füge deiner Aufwertungsleiste das %BOMB%-Symbol hinzu."""
+            text: """Nur für Y-Flügler.%LINEBREAK%Füge deiner Aufwertungsleiste das %BOMB%-Symbol hinzu."""
+            ship: "Y-Flügler"
         '"Genius"':
             name: '"Genie"'
             text: """Wenn du eine Bombe ausgerüstet hast, die gelegt werden kann, sobald du ein Manöver aufdeckst, darfst du die Bombe legen, nachdem du dein Manöver ausgeführt hast."""
@@ -1803,7 +1805,8 @@
             ship: "Medium-Transporter GR-75"
             text: """Erhöhe deinen Hüllenwert um 2 und deinen Schildwert um 1."""
         "B-Wing/E2":
-            text: """Füge deiner Aufwertungsleiste das %CREW%-Symbol hinzu."""
+            text: """Nur für B-Flügler.%LINEBREAK%Füge deiner Aufwertungsleiste das %CREW%-Symbol hinzu."""
+            ship: "B-Flügler"
         "Countermeasures":
             name: "Gegenmassnahmen"
             text: """%DE_LARGESHIPONLY%%LINEBREAK%Zu Beginn der Kampfphase kannst du diese Karte ablegen, um deine Wendigkeit bis zum Ende der Runde um 1 zu erhöhen. Dann darfst du 1 feindliche Zielerfassung von deinem Schiff entfernen."""
@@ -1894,7 +1897,8 @@
             text: '''Nur für CR90-Korvette (Bug).%LINEBREAK%Wenn du die Aktion Koordination durchführst, kannst du 2 freundliche Schiffe wählen (anstatt 1). Jedes dieser Schiffe darf 1 freie Aktion durchführen.'''
         "A-Wing Test Pilot":
             name: "Erfahrener Testpilot"
-            text: '''Nur für A-Wing.%LINEBREAK%Füge deiner Aufwertungsleiste 1 %ELITE%-Symbol hinzu.

Du darfst jede %ELITE%-Aufwertung nur ein Mal ausrüsten. Du kannst diese Karte nicht verwenden, wenn dein Pilotenwert "1" oder niedriger ist.'''
+            text: '''Nur für A-Flügler.%LINEBREAK%Füge deiner Aufwertungsleiste 1 %ELITE%-Symbol hinzu.

Du darfst jede %ELITE%-Aufwertung nur ein Mal ausrüsten. Du kannst diese Karte nicht verwenden, wenn dein Pilotenwert "1" oder niedriger ist.'''
+            ship: "A-Flügler"
         "Tantive IV":
             ship: "CR90-Korvette (Bug)"
             text: '''Nur für CR90-Korvette (Bug).%LINEBREAK%Die Aufwertungsleiste deiner Bugsektion erhält 1 zusätzliches %CREW% und 1 zusätzliches %TEAM% .'''
@@ -1934,7 +1938,8 @@
             text: '''Nur für Aggressor.%LINEBREAK%Du bekommst die Pilotenfähigkeiten aller anderen freundlichen Schiffe mit der Aufwertungskarte IG-2000 (zusätzlich zu deiner eigenen Pilotenfähigkeit).'''
         "BTL-A4 Y-Wing":
             name: "BTL-A4-Y-Wing"
-            text: '''Nur für Y-Wing.%LINEBREAK%Du darfst Schiffe außerhalb deines Feuerwinkels nicht angreifen. Nachdem du einen Angriff mit deinen Primärwaffen durchgeführt hast, darfst du sofort einen weiteren Angriff mit einer %TURRET%-Sekundärwaffe durchführen.'''
+            text: '''Nur für Y-Flügler.%LINEBREAK%Du darfst Schiffe außerhalb deines Feuerwinkels nicht angreifen. Nachdem du einen Angriff mit deinen Primärwaffen durchgeführt hast, darfst du sofort einen weiteren Angriff mit einer %TURRET%-Sekundärwaffe durchführen.'''
+            ship: "Y-Flügler"
         "Andrasta":
             text: '''Nur für Firespray-31.%LINEBREAK%Füge deiner Aufwertungsleiste zwei weitere %BOMB%-Symbole hinzu.'''
         "TIE/x1":


add 'isMobile' function

add 'isMobile' function

--- lib/library.coffee
+++ lib/library.coffee
@@ -42,6 +42,10 @@
 getBounds =->
   frame = [parseInt(document.documentElement.clientWidth - 1), parseInt(document.documentElement.clientHeight - 1)]
   return frame
+
+isMobile =->
+    mobile = if (_useragent.match(/.*iphone.*/) || _useragent.match(/.*android.*/)) then true else false
+    return mobile

 getKeyDirection =->
   dir = {


Change karma conf to adapt latest commonjs-require version.

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -9,7 +9,6 @@
     files: [
       # -- commonjs loader --
       'node_modules/commonjs-require/commonjs-require.js'
-      'node_modules/commonjs-require/node-module-emulator.js'

       # -- wrapped by commonjs --
       'node_modules/expect.js/expect.js'