:white_check_mark: Add specs

:white_check_mark: Add specs

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -721,3 +721,85 @@
         pane.destroyItem(item2)
         expect(pane.getItems().length).toBe 1
         expect(tabBar.element).toHaveClass 'hidden'
+
+  describe "integration with version control systems", ->
+    [repository, tab] = []
+
+    beforeEach ->
+      atom.config.set "tabs.enableVcsColoring", true
+      tab = tabBar.tabAtIndex 1
+      repository = atom.project.getRepo()
+      spyOn(repository, 'isPathIgnored')
+      spyOn(repository, 'getCachedPathStatus')
+      repository.getCachedPathStatus.andReturn 128
+
+    describe "when working inside a VCS repository", ->
+      it "adds custom style for new items", ->
+        tab.updateVcsStatus(repository)
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-added"
+
+      it "adds custom style for modified items", ->
+        repository.getCachedPathStatus.andReturn 256
+        tab.updateVcsStatus(repository)
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-modified"
+
+      it "adds custom style for ignored items", ->
+        repository.isPathIgnored.andReturn true
+        tab.updateVcsStatus(repository)
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-ignored"
+
+      it "does not add any styles for items not in the repository", ->
+        tab = tabBar.tabForItem(item1)
+        tab.path = '/some/path'
+        tab.setupVcsStatus()
+
+        expect(tabBar.find('.tab:eq(0) .title')).not.toHaveClass "status-added"
+        expect(tabBar.find('.tab:eq(0) .title')).not.toHaveClass "status-modified"
+        expect(tabBar.find('.tab:eq(0) .title')).not.toHaveClass "status-ignored"
+
+    describe "when changes in item statuses are notified", ->
+      it "updates status for items in the repository", ->
+        spyOn(tab, 'updateVcsStatus').andCallThrough()
+        expect(tab.updateVcsStatus.calls.length).toEqual 0
+        repository.emitter.emit 'did-change-statuses'
+        expect(tab.updateVcsStatus.calls.length).toEqual 1
+
+      it "updates the status of an item if it has changed", ->
+        expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-modified"
+        repository.emitter.emit 'did-change-status', {path: tab.path, pathStatus: 256}
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-modified"
+        expect(repository.getCachedPathStatus.calls.length).toBe 0
+
+      it "does not update status for items not in the repository", ->
+        tab = tabBar.tabForItem(item1)
+        tab.path = '/some/path'
+        tab.setupVcsStatus()
+
+        spyOn(tab, 'updateVcsStatus').andCallThrough()
+        repository.emitter.emit 'did-change-statuses'
+        expect(tab.updateVcsStatus.calls.length).toEqual 0
+
+    describe "when an item is saved", ->
+      it "does not update VCS subscription if the item's path remains the same", ->
+        spyOn(tab, 'setupVcsStatus').andCallThrough()
+        tab.item.buffer.emitter.emit( 'did-save', {path: tab.path})
+        expect(tab.setupVcsStatus.calls.length).toBe 0
+
+      it "updates VCS subscription if the item's path has changed", ->
+        spyOn(tab, 'setupVcsStatus').andCallThrough()
+        tab.item.buffer.emitter.emit( 'did-save', {path: '/some/other/path'})
+        expect(tab.setupVcsStatus.calls.length).toBe 1
+
+    describe "when enableVcsColoring changes in package settings", ->
+      it "removes status from the tab if enableVcsColoring is set to false", ->
+        tab.setupVcsStatus()
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-added"
+        atom.config.set "tabs.enableVcsColoring", false
+        expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-added"
+
+      it "adds status to the tab if enableVcsColoring is set to true", ->
+        atom.config.set "tabs.enableVcsColoring", false
+        tab.setupVcsStatus()
+        expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-added"
+        atom.config.set "tabs.enableVcsColoring", true
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-added"


icon snippet

icon snippet

--- snippets/imports.cson
+++ snippets/imports.cson
@@ -79,3 +79,6 @@
   'Import Numeral':
     'prefix': 'impn'
     'body': 'import numeral from \'numeral\''
+  'Import React Icons':
+    'prefix': 'impicon'
+    'body': 'import $1Icon from \'react-icons/lib/md/$2\''


Don't automatically switch into 'just_you' mode when opening a proposal by url

--- @client/opinion_views.coffee
+++ @client/opinion_views.coffee
@@ -659,7 +659,8 @@

     is_admin = fetch('/current_user').is_admin
-    show_others = (!customization('hide_opinions') || is_admin) && !@props.disable_switching
+    show_others = (!customization('hide_opinions') || is_admin) # && !@props.disable_switching
+
     show_all_not_available = !show_others

     view_buttons = [
@@ -667,7 +668,7 @@
         key: 'all'
         label: translator 'opinion_views.view_buttons_all', 'All opinions'
         callback: clear_all
-        disabled: !show_others
+        disabled: !show_others || @props.disable_switching
       }
       {
         key: 'you'
@@ -680,7 +681,7 @@
       {
         key: 'custom'
         label: translator 'opinion_views.view_buttons_custom', 'Custom view'
-        disabled: !show_others
+        disabled: !show_others || @props.disable_switching
         callback: (item, previous_state) =>
           if previous_state == 'custom'
             if user_has_set_a_view()


Using bindObservable for selects

Using bindObservable for selects

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -50,27 +50,24 @@

   switch element.nodeName
     when "SELECT"
-      # TODO: Figure out a way to use bindObservable in here to consolidate our leak prevention
-      updateSelected = (newValue) ->
+      element.oninput = element.onchange = ->
+        {value:optionValue, _value} = @children[@selectedIndex]
+
+        value(_value or optionValue)
+
+      update = (newValue) ->
         # This is so we can hold a non-string object as a value of the select element
         element._value = newValue

-        if (options = element._options) and (typeof newValue is "object")
+        if (options = element._options)
           if newValue.value?
-            element.value = newValue.value
+            element.value = newValue.value?() or newValue.value
           else
             element.selectedIndex = options.indexOf(newValue)
         else
           element.value = newValue

-      update = ->
-        {value:optionValue, _value} = @children[@selectedIndex]
-
-        value(_value or optionValue)
-
-      element.oninput = element.onchange = update
-      updateSelected(value())
-      value.observe updateSelected
+      bindObservable element, value, context, update
     else
       # Because firing twice with the same value is idempotent just binding both
       # oninput and onchange handles the widest range of inputs and browser


Don't remove referenced packaged dependencies

--- src/cleaner.coffee
+++ src/cleaner.coffee
@@ -23,14 +23,14 @@
     catch error
       return

-    _.extend(allDependencies, dependencies, packageDependencies)
+    _.extend(allDependencies, dependencies)

     modulesPath = path.join(modulePath, 'node_modules')
     for installedModule in fs.list(modulesPath) when installedModule isnt '.bin'
       @getDependencies(path.join(modulesPath, installedModule), allDependencies)

   getModulesToRemove: ->
-    {devDependencies, dependencies} = CSON.readFileSync(CSON.resolve('package')) ? {}
+    {devDependencies, dependencies, packageDependencies} = CSON.readFileSync(CSON.resolve('package')) ? {}
     devDependencies ?= {}
     dependencies ?= {}

@@ -47,6 +47,7 @@
     for installedModule in installedModules
       continue if dependencies.hasOwnProperty(installedModule)
       continue if devDependencies.hasOwnProperty(installedModule)
+      continue if packageDependencies.hasOwnProperty(installedModule)
       modulesToRemove.push(installedModule)

     modulesToRemove


added a TODO for a dodgy line in Complex.acos()

--- src/complex.coffee
+++ src/complex.coffee
@@ -93,6 +93,7 @@
       x
       sqrt subtract pow(x, 2), 1
     )
+    # TODO look up the algorithm for determining the sign of arccos; the following line is dubious
     if r instanceof Complex and (r.re < 0 or (r.re is 0 and r.im < 0)) then negate r else r

   @atan = atan = (x) -> # arctan x = (i/2) (ln(1-ix) - ln(1+ix))


_replaceElements() should have used a list comprehension instead of a simple loop.

--- src/control.coffee
+++ src/control.coffee
@@ -204,7 +204,7 @@
   ###
   _replaceElements: ( $existing, $replacement ) ->
     # Gather the existing IDs.
-    ids = $( element ).prop "id" for element in $existing
+    ids = ( $( element ).prop "id" for element in $existing )
     $new = $replacement.replaceAll( $existing )
     # Put IDs onto new elements.
     for element, i in $new


fixes for coffeelint

fixes for coffeelint

--- src/crampon.coffee
+++ src/crampon.coffee
@@ -1,10 +1,10 @@
 #  crampon
 #  https://github.com/couchand/crampon
-#
+#
 #  Copyright (c) 2013 Andrew Couch
 #  Licensed under the MIT license.

-'use strict';
+'use strict'

 exports.awesome = ->
   'awesome'


Revert "Config should be frozen in app."

This reverts commit 76d2c39acbb20f62d44f5469d14787640eda09f2.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -72,7 +72,6 @@
   try
     {config} = require sysPath.resolve configPath
     setConfigDefaults config
-    Object.freeze config
   catch error
     logger.error "couldn\'t load config #{configPath}. #{error}"
     config = null


Revert "Add --no-save to npm install arguments"

This reverts commit e7ffe9b38824519df8c09b5519da72bfb4ade935.

--- src/install.coffee
+++ src/install.coffee
@@ -97,7 +97,6 @@
     installArgs = ['--globalconfig', config.getGlobalConfigPath(), '--userconfig', config.getUserConfigPath(), 'install']
     installArgs.push(moduleURI)
     installArgs.push("--runtime=electron")
-    installArgs.push("--no-save")
     installArgs.push("--target=#{@electronVersion}")
     installArgs.push("--arch=#{config.getElectronArch()}")
     installArgs.push("--global-style") if installGlobally
@@ -192,7 +191,6 @@

   forkInstallCommand: (options, callback) ->
     installArgs = ['--globalconfig', config.getGlobalConfigPath(), '--userconfig', config.getUserConfigPath(), 'install']
-    installArgs.push("--no-save")
     installArgs.push("--runtime=electron")
     installArgs.push("--target=#{@electronVersion}")
     installArgs.push("--arch=#{config.getElectronArch()}")


video capture

video capture

--- client/Framework/components/image/KDPhotoCapture.coffee
+++ client/Framework/components/image/KDPhotoCapture.coffee
@@ -1,109 +0,0 @@
-class KDPhotoCapture extends JView
-
-  constructor: (options={}, data)->
-
-    options.cssClass    or= "kdphotocapture"
-    options.screenFlash or= yes
-
-    super options, data
-
-    @on "viewAppended", =>
-      @context = @picture.getElement().getContext "2d"
-      @getUserMedia()
-
-    @on "error", (error)->
-      @setClass "forbidden"
-      new KDNotificationView
-        type    : "tiny"
-        title   : "Your browser doesn't allow to use camera."
-
-    @on "snap", =>
-      @video.setClass "invisible"
-
-    @video = new KDCustomHTMLView
-      tagName   : "video"
-      attributes:
-        autoplay: yes
-
-    @picture = new KDCustomHTMLView
-      tagName   : "canvas"
-
-    @button = new KDButtonView
-      title     : "Snap Photo"
-      cssClass  : "snap-photo-button hidden"
-      callback  : =>
-        @button.hide()
-        @retake.show()
-        @takePicture()
-
-    @retake = new KDButtonView
-      title     : "Resnap"
-      cssClass  : "snap-photo-retake hidden"
-      callback  : =>
-        @button.show()
-        @retake.hide()
-        @reset()
-
-  reset: ->
-    @video.unsetClass "invisible"
-
-  autoResize: ->
-    video = @video.getElement()
-    size  =
-      width : video.clientWidth
-      height: video.clientHeight
-
-    @picture.setDomAttributes size
-    @setSize size
-
-  setVideoStream: (stream)->
-    video = @video.getElement()
-    video.src = stream
-    video.play()
-    video.addEventListener "playing", =>
-      @show()
-      @button.show()
-      @autoResize()
-
-  getUserMedia: ->
-    _onError = (error)=> @emit "error", error
-    if navigator.getUserMedia
-      navigator.getUserMedia video: yes, (stream)=>
-        @setVideoStream stream
-      , _onError
-    else
-      navigator.webkitGetUserMedia video: yes, (stream)=>
-        @setVideoStream window.webkitURL.createObjectURL stream
-      , _onError
-
-  flash: ->
-    flash  = new KDView
-    $flash = flash.$()
-    $flash.css
-      zIndex          : 10002
-      backgroundColor : "#ffffff"
-      position        : "absolute"
-      top             : 0
-      left            : 0
-      opacity         : 0.95
-    KDView.appendToDOMBody flash
-    $flash.fadeOut -> flash.destroy()
-
-  takePicture: ->
-    video   = @video.getElement()
-    picture = @picture.getElement()
-
-    {screenFlash} = @getOptions()
-    @flash() if screenFlash
-    @autoResize()
-
-    @context.drawImage video, 0, 0, video.clientWidth, video.clientHeight
-    @emit "snap", picture.toDataURL(), picture
-
-  pistachio: ->
-    """
-    {{> @button}}
-    {{> @retake}}
-    {{> @video}}
-    {{> @picture}}
-    """


reactivity: chatlistitem: edit mode test is added

--- client/activity/lib/components/chatlistitem/test.coffee
+++ client/activity/lib/components/chatlistitem/test.coffee
@@ -14,7 +14,9 @@
 MessageItemMenu         = require 'activity/components/messageitemmenu'
 EmbedBox                = require 'activity/components/embedbox'
 ChatInputEmbedExtractor = require 'activity/components/chatinputembedextractor'
-
+ChannelToken            = require 'activity/components/chatinputwidget/tokens/channeltoken'
+EmojiToken              = require 'activity/components/chatinputwidget/tokens/emojitoken'
+MentionToken            = require 'activity/components/chatinputwidget/tokens/mentiontoken'

 describe 'ChatListItem', ->

@@ -45,6 +47,25 @@
       _id           : 2
       profile       : { nickname : 'john', firstName : '', lastName : '' }
       isIntegration : yes
+
+  editingMessage = toImmutable
+    id              : 2
+    body            : 'Humans visited Arctic earlier than thought'
+    __isEditing     : yes
+    interactions    : { like : { actorsCount : 4 } }
+    repliesCount    : 1
+    createdAt       : '2016-01-15'
+    link            :
+      link_embed    : 'https://www.sciencenews.org/'
+      link_embed    :
+        type        : 'link'
+        description : 'Science News'
+        title       : 'Science News'
+    account         :
+      _id           : 2
+      profile       : { nickname : 'john', firstName : '', lastName : '' }
+      isIntegration : yes
+

   describe '::render', ->

@@ -112,3 +133,20 @@
       )
       embedBox = TestUtils.findRenderedComponentWithType result, EmbedBox
       expect(embedBox.props.data).toEqual messageWithEmbedData.get('link').toJS()
+
+
+    it 'renders a chat item in edit mode', ->
+
+      result = TestUtils.renderIntoDocument(
+        <ChatListItem message={message} />
+      )
+      expect(-> TestUtils.findRenderedComponentWithType result, ChatInputEmbedExtractor).toThrow()
+
+      result = TestUtils.renderIntoDocument(
+        <ChatListItem message={editingMessage} channelId=1 />
+      )
+      inputEmbedExtractor = TestUtils.findRenderedComponentWithType result, ChatInputEmbedExtractor
+      expect(inputEmbedExtractor.props.messageId).toEqual editingMessage.get 'id'
+      expect(inputEmbedExtractor.props.channelId).toEqual 1
+      expect(inputEmbedExtractor.props.value).toEqual editingMessage.get 'body'
+      expect(inputEmbedExtractor.props.tokens).toEqual [ChannelToken, EmojiToken, MentionToken]


NFinderItem: Always focus to rename input.

Otherwise focus was remaining on the finder item and pressing enter was opening the file.

--- client/finder/lib/filetree/itemviews/nfinderitem.coffee
+++ client/finder/lib/filetree/itemviews/nfinderitem.coffee
@@ -96,9 +96,9 @@

     @setClass "being-edited"

-    @beingEdited  = yes
-    @callback     = callback
-    data          = @getData()
+    @beingEdited = yes
+    @callback    = callback
+    data         = @getData()

     @childView.hide()

@@ -109,12 +109,13 @@
       @callback? newValue
       @resetView()

-    ext = ".#{FSHelper.getFileExtension data.name}"
+    ext       = ".#{FSHelper.getFileExtension data.name}"
+    { input } = @renameView

     if (index = data.name.indexOf ext) > 0
-      @renameView.input.getElement().setSelectionRange 0, index
-    else
-      @renameView.input.setFocus()
+      input.getElement().setSelectionRange 0, index
+
+    input.setFocus()

   showProgressView: (progress, determinate=yes)->


landing: login: remove bg photo credits

landing: login: remove bg photo credits

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -181,7 +181,7 @@

-      Acceptable user policyCopyright/DMCA guidelinesTerms of servicePrivacy policyphoto by #{backgroundImages[backgroundImageNr][0]}
+      Acceptable user policyCopyright/DMCA guidelinesTerms of servicePrivacy policy

     """


Fixed bug with plugin version check skip

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -2673,7 +2673,7 @@
 					# Unsupported?
 					if unsupported
 						# Version?
-						if unsupported in ['version-docpad','version-pugin'] and config.skipUnsupportedPlugins is false
+						if unsupported in ['version-docpad','version-plugin'] and config.skipUnsupportedPlugins is false
 							docpad.log 'warn', util.format(locale.pluginContinued, pluginName)
 						else
 							# Type?


Minor

Minor

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -3524,10 +3524,7 @@
 			# Regenerat after a while
 			regenerateTimer = setTimeout(performGenerate, config.regenerateDelay)

-		performGenerate = (opts) ->
-			# Prepare
-			opts or= {}
-
+		performGenerate = (opts={}) ->
 			# Do not reset when we do this generate
 			opts.reset ?= false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -3,11 +3,11 @@
 test "#closest", ->
   engine = Engine()

-  p = engine.add
+  player = engine.add
     x: 0
     y: 0

-  obj2 = engine.add
+  engine.add
     rad: "Awesome"
     x: 5
     y: 10


Added instanceConfig to plugin base class

--- src/lib/plugin.coffee
+++ src/lib/plugin.coffee
@@ -19,6 +19,7 @@

 	# Plugin config
 	config: {}
+	instanceConfig: {}

 	# Plugin priority
 	priority: 500
@@ -31,7 +32,8 @@
 		@docpad = docpad

 		# Swap out our configuration
-		@config = balUtil.deepExtendPlainObjects({},@config)
+		@config = balUtil.deepClone(@config)
+		@instanceConfig = balUtil.deepClone(@instanceConfig)
 		@initialConfig = @config
 		@setConfig(config)

@@ -44,6 +46,14 @@
 		# Chain
 		@

+	# Set Instance Configuration
+	setInstanceConfig: (instanceConfig) ->
+		# Merge in the instance configurations
+		if instanceConfig
+			balUtil.safeDeepExtendPlainObjects(@instanceConfig, instanceConfig)
+			balUtil.safeDeepExtendPlainObjects(@config, instanceConfig)  if @config
+		@
+
 	# Set Configuration
 	setConfig: (instanceConfig=null) =>
 		# Prepare
@@ -51,8 +61,11 @@
 		userConfig = @docpad.config.plugins[@name]
 		@config = @docpad.config.plugins[@name] = {}

+		# Instance config
+		@setInstanceConfig(instanceConfig)  if instanceConfig
+
 		# Merge configurations
-		configPackages = [@initialConfig, userConfig, instanceConfig]
+		configPackages = [@initialConfig, userConfig, @instanceConfig]
 		configsToMerge = [@config]
 		docpad.mergeConfigurations(configPackages,configsToMerge)


Try and use GA event tracking for the About link.

--- content/scripts/application.coffee
+++ content/scripts/application.coffee
@@ -31,6 +31,10 @@
     $(this).attr("href")
   ).tipTip(
     maxWidth: "auto"
+  )
+
+  $("#about").click(->
+    _gaq.push(['_trackEvent', 'About', 'Click'])
   )

 initialize_map = (callback) ->


Add Chapter/Section toggle button

Add Chapter/Section toggle button

--- src/components/learning-guide.cjsx
+++ src/components/learning-guide.cjsx
@@ -34,9 +34,8 @@
   displayUnit: (unit, chapter) ->
     @setState({unit, chapter: chapter})

-  displayChapter: ->
-    if @state.showAll
-      @setState({showAll:false}, -> @loadChart())
+  toggleChapter: ->
+    @setState({showAll: not @state.showAll}, -> @loadChart())

   displayTopic: ->
     {courseId} = @props
@@ -73,10 +72,10 @@

           <PracticeButton showAll={@state.showAll} courseId={@props.courseId} pageIds={unit.page_ids}/>

-      chapterButton =
+      chapterToggleButton =

-          <BS.Button className="chapter-button" bsStyle='primary' onClick={@displayChapter}>
-            View This Chapter
+          <BS.Button className="chapter-button" onClick={@toggleChapter}>
+            {if @state.showAll then 'Expand Chapter' else 'Back to overall'}
           </BS.Button>

       helpText =
@@ -98,7 +97,7 @@

             {problemsWorked}
             {practiceButton}
-            {if @state.showAll then chapterButton}
+            {chapterToggleButton}

           {helpText}


Fix typo in new tests

Fix typo in new tests

--- modules/titlebar/test/spec.coffee
+++ modules/titlebar/test/spec.coffee
@@ -50,20 +50,20 @@

   it 'should set the active section by node correctly', ->
     titlebar = new hx.TitleBar(hx.select('#fixture').select('.hx-heading').node())
-    node = hx.selectAll('.hx-titlebar-link').nodes(3)
+    node = hx.selectAll('.hx-titlebar-link').node(3)
     titlebar.active(node)
     hx.selectAll('.hx-titlebar-link').classed('hx-selected').should.eql([false, false, false, true, false])

   it 'should set the active section by ID correctly', ->
     titlebar = new hx.TitleBar(hx.select('#fixture').select('.hx-heading').node())
-    hx.select(hx.selectAll('.hx-titlebar-link').nodes(2))
+    hx.select(hx.selectAll('.hx-titlebar-link').node(2))
       .attr('id', '#titlebar-active-test')
     titlebar.active('#titlebar-active-test')
     hx.selectAll('.hx-titlebar-link').classed('hx-selected').should.eql([false, false, true, false, false])

   it 'should set the active section by Selection correctly', ->
     titlebar = new hx.TitleBar(hx.select('#fixture').select('.hx-heading').node())
-    sel = hx.select(hx.selectAll('.hx-titlebar-link').nodes(2))
+    sel = hx.select(hx.selectAll('.hx-titlebar-link').node(2))
     titlebar.active(sel)
     hx.selectAll('.hx-titlebar-link').classed('hx-selected').should.eql([false, false, true, false, false])


Rework cisual mode; tweaks and fixes.

Rework cisual mode; tweaks and fixes.

--- content_scripts/mode_visual.coffee
+++ content_scripts/mode_visual.coffee
@@ -22,18 +22,6 @@
         afterText[afterText.length - 1]
     else
       beforeText[0] # The existing range selection is backwards.
-
-  # As above, but backwards.
-  getNextBackwardCharacter: ->
-    beforeText = @selection.toString()
-    if beforeText.length == 0 or @getDirection() == backward
-      @selection.modify "extend", backward, character
-      afterText = @selection.toString()
-      if beforeText != afterText
-        @selection.modify "extend", forward, character
-        afterText[0]
-    else
-      beforeText[beforeText.length - 1] # The existing range selection is forwards.

   # Test whether the character following the focus is a word character (and leave the selection unchanged).
   nextCharacterIsWordCharacter: do ->
@@ -201,7 +189,7 @@
     "N": (count) -> @find count, true
     "/": ->
       @exit()
-      new FindMode(returnToViewport: true).onExit => new VisualMode
+      new FindMode(returnToViewport: true).onExit -> new VisualMode

     "y": -> @yank()
     "Y": (count) -> @movement.selectLine count; @yank()
@@ -283,7 +271,7 @@
         @movement.runMovement command for [0...count] by 1
       when "function"
         command count
-    return # Prevent CoffeeScript building an array (for the "string" case, above).
+    @movement.scrollIntoView()

   find: (count, backwards) =>
     initialRange = @selection.getRangeAt(0).cloneRange()


remove describe.only

remove describe.only

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -171,7 +171,7 @@
       Bacon.fromCallback
     )

-describe.only "Bacon.fromNodeCallback", ->
+describe "Bacon.fromNodeCallback", ->
   describe "makes an EventStream from function that takes a node-style callback", ->
     expectStreamEvents(
       ->


move browser check out

move browser check out

--- spec/SetupPeer.coffee
+++ spec/SetupPeer.coffee
@@ -4,6 +4,12 @@
   SetupPeer = require '../components/SetupPeer.coffee'
 else
   SetupPeer = require 'noflo-peer/components/SetupPeer.js'
+
+describe 'Browser check', ->
+  it 'should be a compatible browser', ->
+    peer = require('peerjs')
+    supports = peer.util.supports
+    chai.expect(supports.data).to.be.true

 describe 'SetupPeer component', ->
   c = null
@@ -36,11 +42,6 @@
       chai.expect(c.outPorts.close).to.be.an 'object'
       chai.expect(c.outPorts.server_error).to.be.an 'object'
       chai.expect(c.outPorts.peer_error).to.be.an 'object'
-    it 'should be a compatible browser', ->
-      p = require('peerjs')
-      supports = p.util.supports
-      chai.expect(supports.audioVideo).to.be.true
-      chai.expect(supports.data).to.be.true

   describe 'before connected to server', ->
     connect = null


Add spec for #634

Add spec for #634

--- spec/node-spec.coffee
+++ spec/node-spec.coffee
@@ -108,3 +108,11 @@
         client.on 'error', (error) ->
           assert.equal error.code, 'ECONNREFUSED'
           done()
+
+  describe 'Buffer', ->
+    it 'can be created from WebKit external string', ->
+      p = document.createElement 'p'
+      p.innerText = '闲云潭影日悠悠，物换星移几度秋'
+      b = new Buffer(p.innerText)
+      assert.equal b.toString(), '闲云潭影日悠悠，物换星移几度秋'
+      assert.equal Buffer.byteLength(p.innerText), 45


Remove logging from spec

Remove logging from spec

--- spec/pane-spec.coffee
+++ spec/pane-spec.coffee
@@ -104,7 +104,6 @@

         it "creates a new view with the item", ->
           initialViewCount = pane.itemViews.find('.test-view').length
-          console.log 'initial', initialViewCount

           model1 =
             id: 'test-model-1'


Assert stack trace location

Assert stack trace location

--- spec/task-spec.coffee
+++ spec/task-spec.coffee
@@ -47,11 +47,13 @@

   it "reports deprecations in tasks", ->
     jasmine.snapshotDeprecations()
-    task = new Task(require.resolve('./fixtures/task-handler-with-deprecations'))
+    handlerPath = require.resolve('./fixtures/task-handler-with-deprecations')
+    task = new Task(handlerPath)

     waitsFor (done) -> task.start(done)

     runs ->
       deprecations = Grim.getDeprecations()
       expect(deprecations.length).toBe 1
+      expect(deprecations[0].getStacks()[0][1].fileName).toBe handlerPath
       jasmine.restoreDeprecationsSnapshot()


crawler: change activity if statement

crawler: change activity if statement

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -659,7 +659,7 @@
     if name is 'Develop'
       return res.redirect 301, '/IDE'

-    if name in ['Activity']
+    if name is 'Activity'
       isLoggedIn req, res, (err, loggedIn, account)->

         return  serveHome req, res, next  if loggedIn


remove the last of the non-component JS

remove the last of the non-component JS

--- frontend/app/js/player-ctrl.coffee
+++ frontend/app/js/player-ctrl.coffee
@@ -1,8 +0,0 @@
-mod = angular.module('mothership')
-
-mod.controller 'PlayerCtrl', ($scope, player, playlist) ->
-  'use strict'
-  ctrl = this
-
-  $scope.player   = player
-  $scope.playlist = playlist


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -149,7 +149,7 @@
     if not I[options.name]
       I[options.name] = 100

-    if not I["max#{I.name.capitalize()}"]
+    if not I["max#{options.name.capitalize()}"]
       if I[I.name]
         I["max#{I.name.capitalize()}"] = I[name]
       else


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -1,6 +1,6 @@
 ###*
 The Metered module provides a simple drop-in
-meter ui to track a
+meter ui to track arbitrar

 player = Core


Use JSON example since package.json is target

--- src/package.coffee
+++ src/package.coffee
@@ -411,10 +411,11 @@
       deprecate """
         Use `activationCommands` instead of `activationEvents` in your package.json
         Commands should be grouped by selector as follows:
-        ```coffee
-          activationCommands:
-            "atom-workspace": ["foo:bar", "foo:baz"]
+        ```json
+          "activationCommands": {
+            "atom-workspace": ["foo:bar", "foo:baz"],
             "atom-text-editor": ["foo:quux"]
+          }
         ```
       """
       if _.isArray(@metadata.activationEvents)


More work on new thumb gallery preset

More work on new thumb gallery preset

--- src/presets.coffee
+++ src/presets.coffee
@@ -140,14 +140,18 @@

     thumbGallery:
       # Custom options
-      myColumns: 3
-      myRows: 3
-      myItemPadding: 5
+      special:
+        columns: 3
+        rows: 3
+        groupSize: 10
+        itemPadding: 5
       # Standard rondell options
       visibleItems: 9
-      center:
-        top: 200
-        left: 550
+      wrapIndices: false
+      currentLayer: 1
+      center:
+        top: 215
+        left: 250
       size:
         height: 430
         width: 800
@@ -157,27 +161,50 @@
           x: 10
           y: 255
       itemProperties:
-        delay: 30
+        delay: 40
         sizeFocused:
           width: 480
-          height: 390
+          height: 420
         size:
           width: 94
           height: 126
       scrollbar:
         enabled: true
-        width: 300
-      funcTop: (l, r, i) ->
-        row = (i - 1) % r.myRows
-        r.myItemPadding + row * (r.itemProperties.size.height + r.myItemPadding)
-      funcLeft: (l, r, i) ->
-        column = Math.floor(r.myColumns / 2) + Math.ceil(i / r.myRows) - Math.ceil((i - l) / r.myRows)
-        r.myItemPadding + column * (r.itemProperties.size.width + r.myItemPadding)
+        style:
+          width: 292
+          right: 0
+          bottom: 0
       funcDiff: (d, r, i) ->
         Math.abs d
       funcOpacity: (l, r, i) ->
-        column = Math.floor(r.myColumns / 2) + Math.ceil(i / r.myRows) - Math.ceil((i - l) / r.myRows)
-        if column < 0 or column >= r.myColumns then 0 else 0.8
+        # Show items in the same group as the focused one
+        if Math.floor((i - 1) / r.special.groupSize) is \
+          Math.floor((i - 1 - l) / r.special.groupSize) then 0.8 else 0
+      funcTop: (l, r, i) ->
+        # Shift items right of the focused item and a special fix for the last item in the first group
+        if l > 0 or i is r.special.groupSize
+          i--
+          l--
+        # Hack to fix animation of the focused element in a group
+        i = 1 if i % r.special.groupSize is 0
+        # Compute items vertical offset
+        r.special.itemPadding + Math.floor(((i - 1) % r.special.groupSize) / r.special.rows) \
+          * (r.itemProperties.size.height + r.special.itemPadding)
+
+      funcLeft: (l, r, i) ->
+        # Shift elements right of the focused item and a special fix for the last item in the first group
+        if l > 0 or i is r.special.groupSize
+          i--
+          l--
+        # Hack to fix animation of the focused element in a group
+        i = 1 if i % r.special.groupSize is 0
+        # Get items column
+        column = ((i - 1) % r.special.groupSize) % r.special.columns
+        # Get the group difference
+        groupOffset = Math.floor((i - 1) / r.special.groupSize) - Math.floor((i - 1 - l) / r.special.groupSize)
+        # Compute final column positioning
+        500 + r.special.itemPadding + (column + r.special.columns * groupOffset) \
+          * (r.itemProperties.size.width + r.special.itemPadding)

     slider:
       visibleItems: 1


Improved error logging on invalid compilation flag

--- src/program.coffee
+++ src/program.coffee
@@ -51,7 +51,7 @@
     calculateCompilationFlags = (execFile, ext, programIndex, isGuessed) =>
         invalidFlag = (flag) =>
             unless isGuessed
-                logger.e "Invalid compiler flag description #{flag} (#{execFile})", exit: yes
+                logger.e "Invalid compiler flag description #{flag} (#{styler.id execFile})", exit: yes, printStack: no
             else ""

         ext = ext[1..].toLowerCase() # Get rid of the dot and put to lowercase


Conditionally include deprecations in Project

--- src/project.coffee
+++ src/project.coffee
@@ -4,7 +4,7 @@
 _ = require 'underscore-plus'
 fs = require 'fs-plus'
 Q = require 'q'
-{deprecate} = require 'grim'
+{includeDeprecations, deprecate} = require 'grim'
 {Model} = require 'theorist'
 {Subscriber} = require 'emissary'
 {Emitter} = require 'event-kit'
@@ -24,12 +24,6 @@
 class Project extends Model
   atom.deserializers.add(this)
   Serializable.includeInto(this)
-
-  @pathForRepositoryUrl: (repoUrl) ->
-    deprecate '::pathForRepositoryUrl will be removed. Please remove from your code.'
-    [repoName] = url.parse(repoUrl).path.split('/')[-1..]
-    repoName = repoName.replace(/\.git$/, '')
-    path.join(atom.config.get('core.projectHome'), repoName)

   ###
   Section: Construction and Destruction
@@ -73,8 +67,10 @@

     @subscribeToBuffer(buffer) for buffer in @buffers

-    Grim.deprecate("Pass 'paths' array instead of 'path' to project constructor") if path?
-    paths ?= _.compact([path])
+    if Grim.includeDeprecations and path?
+      Grim.deprecate("Pass 'paths' array instead of 'path' to project constructor")
+      paths ?= _.compact([path])
+
     @setPaths(paths)

   destroyed: ->
@@ -122,13 +118,6 @@
   onDidAddBuffer: (callback) ->
     @emitter.on 'did-add-buffer', callback

-  on: (eventName) ->
-    if eventName is 'path-changed'
-      Grim.deprecate("Use Project::onDidChangePaths instead")
-    else
-      Grim.deprecate("Project::on is deprecated. Use documented event subscription methods instead.")
-    super
-
   ###
   Section: Accessing the git repository
   ###
@@ -144,9 +133,6 @@
   #     project.repositoryForDirectory.bind(project)))
   # ```
   getRepositories: -> @repositories
-  getRepo: ->
-    Grim.deprecate("Use ::getRepositories instead")
-    @getRepositories()[0]

   # Public: Get the repository for a given directory asynchronously.
   #
@@ -180,9 +166,6 @@
   # Public: Get an {Array} of {String}s containing the paths of the project's
   # directories.
   getPaths: -> rootDirectory.getPath() for rootDirectory in @rootDirectories
-  getPath: ->
-    Grim.deprecate("Use ::getPaths instead")
-    @getPaths()[0]

   # Public: Set the paths of the project's directories.
   #
@@ -197,10 +180,6 @@

     @emit "path-changed"
     @emitter.emit 'did-change-paths', projectPaths
-
-  setPath: (path) ->
-    Grim.deprecate("Use ::setPaths instead")
-    @setPaths([path])

   # Public: Add a path to the project's list of root paths
   #
@@ -257,13 +236,6 @@
   # Public: Get an {Array} of {Directory}s associated with this project.
   getDirectories: ->
     @rootDirectories
-  getRootDirectory: ->
-    Grim.deprecate("Use ::getDirectories instead")
-    @getDirectories()[0]
-
-  resolve: (uri) ->
-    Grim.deprecate("Use `Project::getDirectories()[0]?.resolve()` instead")
-    @resolvePath(uri)

   resolvePath: (uri) ->
     return unless uri
@@ -330,18 +302,6 @@
     @rootDirectories.some (dir) -> dir.contains(pathToCheck)

   ###
-  Section: Searching and Replacing
-  ###
-
-  scan: (regex, options={}, iterator) ->
-    Grim.deprecate("Use atom.workspace.scan instead of atom.project.scan")
-    atom.workspace.scan(regex, options, iterator)
-
-  replace: (regex, replacementText, filePaths, iterator) ->
-    Grim.deprecate("Use atom.workspace.replace instead of atom.project.replace")
-    atom.workspace.replace(regex, replacementText, filePaths, iterator)
-
-  ###
   Section: Private
   ###

@@ -364,12 +324,6 @@

     @bufferForPath(filePath).then (buffer) =>
       @buildEditorForBuffer(buffer, options)
-
-  # Deprecated
-  openSync: (filePath, options={}) ->
-    deprecate("Use Project::open instead")
-    filePath = @resolvePath(filePath)
-    @buildEditorForBuffer(@bufferForPathSync(filePath), options)

   # Retrieves all the {TextBuffer}s in the project; that is, the
   # buffers for all open files.
@@ -479,22 +433,65 @@
         detail: error.message
         dismissable: true

-  # Deprecated: delegate
-  registerOpener: (opener) ->
+if includeDeprecations
+  Project.pathForRepositoryUrl = (repoUrl) ->
+    deprecate '::pathForRepositoryUrl will be removed. Please remove from your code.'
+    [repoName] = url.parse(repoUrl).path.split('/')[-1..]
+    repoName = repoName.replace(/\.git$/, '')
+    path.join(atom.config.get('core.projectHome'), repoName)
+
+  Project::registerOpener = (opener) ->
     deprecate("Use Workspace::addOpener instead")
     atom.workspace.addOpener(opener)

-  # Deprecated: delegate
-  unregisterOpener: (opener) ->
+  Project::unregisterOpener = (opener) ->
     deprecate("Call .dispose() on the Disposable returned from ::addOpener instead")
     atom.workspace.unregisterOpener(opener)

-  # Deprecated: delegate
-  eachEditor: (callback) ->
+  Project::eachEditor = (callback) ->
     deprecate("Use Workspace::observeTextEditors instead")
     atom.workspace.observeTextEditors(callback)

-  # Deprecated: delegate
-  getEditors: ->
+  Project::getEditors = ->
     deprecate("Use Workspace::getTextEditors instead")
     atom.workspace.getTextEditors()
+
+  Project::on = (eventName) ->
+    if eventName is 'path-changed'
+      Grim.deprecate("Use Project::onDidChangePaths instead")
+    else
+      Grim.deprecate("Project::on is deprecated. Use documented event subscription methods instead.")
+    super
+
+  Project::getRepo = ->
+    Grim.deprecate("Use ::getRepositories instead")
+    @getRepositories()[0]
+
+  Project::getPath = ->
+    Grim.deprecate("Use ::getPaths instead")
+    @getPaths()[0]
+
+  Project::setPath = (path) ->
+    Grim.deprecate("Use ::setPaths instead")
+    @setPaths([path])
+
+  Project::getRootDirectory = ->
+    Grim.deprecate("Use ::getDirectories instead")
+    @getDirectories()[0]
+
+  Project::resolve = (uri) ->
+    Grim.deprecate("Use `Project::getDirectories()[0]?.resolve()` instead")
+    @resolvePath(uri)
+
+  Project::scan = (regex, options={}, iterator) ->
+    Grim.deprecate("Use atom.workspace.scan instead of atom.project.scan")
+    atom.workspace.scan(regex, options, iterator)
+
+  Project::replace = (regex, replacementText, filePaths, iterator) ->
+    Grim.deprecate("Use atom.workspace.replace instead of atom.project.replace")
+    atom.workspace.replace(regex, replacementText, filePaths, iterator)
+
+  Project::openSync = (filePath, options={}) ->
+    deprecate("Use Project::open instead")
+    filePath = @resolvePath(filePath)
+    @buildEditorForBuffer(@bufferForPathSync(filePath), options)


Fix deprecated calls and recommendations in deprecated methods

--- src/project.coffee
+++ src/project.coffee
@@ -419,7 +419,7 @@
   # Deprecated: delegate
   registerOpener: (opener) ->
     deprecate("Use Workspace::addOpener instead")
-    atom.workspace.registerOpener(opener)
+    atom.workspace.addOpener(opener)

   # Deprecated: delegate
   unregisterOpener: (opener) ->
@@ -428,10 +428,10 @@

   # Deprecated: delegate
   eachEditor: (callback) ->
-    deprecate("Use Workspace::eachEditor instead")
-    atom.workspace.eachEditor(callback)
+    deprecate("Use Workspace::observeTextEditors instead")
+    atom.workspace.observeTextEditors(callback)

   # Deprecated: delegate
   getEditors: ->
-    deprecate("Use Workspace::getEditors instead")
-    atom.workspace.getEditors()
+    deprecate("Use Workspace::getTextEditors instead")
+    atom.workspace.getTextEditors()


Remove unused repoUrl property

Remove unused repoUrl property

--- src/project.coffee
+++ src/project.coffee
@@ -80,9 +80,6 @@
         @repo.refreshStatus()
     else
       @rootDirectory = null
-
-    if originUrl = @repo?.getOriginUrl()
-      @state.set('repoUrl', originUrl)

     @emit "path-changed"


Bump max size to 2MB

Bump max size to 2MB

--- src/project.coffee
+++ src/project.coffee
@@ -189,8 +189,8 @@
   #
   # Returns a promise that resolves to the {TextBuffer}.
   buildBuffer: (absoluteFilePath) ->
-    if fs.getSizeSync(absoluteFilePath) >= 1048576 # 1MB
-      throw new Error("Atom can only handle files < 1MB, for now.")
+    if fs.getSizeSync(absoluteFilePath) >= 2 * 1048576 # 2MB
+      throw new Error("Atom can only handle files < 2MB, for now.")

     buffer = new TextBuffer({filePath: absoluteFilePath})
     @addBuffer(buffer)


adding custom classloader logic back in, still fails

the class is already loaded by the system CL, so we don't initialize it
ourselves and this path is never taken

--- src/runtime.coffee
+++ src/runtime.coffee
@@ -250,18 +250,34 @@
           @load_class type.component_type, dyn
           @class_states[cls] = new ClassState @class_states[type.component_type.toClassString()].loader
       else
-        # bootstrap class loader
-        @class_states[cls] = new ClassState null
-        class_file = @read_classfile cls
-        if not class_file? or wrong_name = (class_file.this_class.toClassString() != cls)
-          msg = cls
-          if wrong_name
-            msg += " (wrong name: #{class_file.this_class.toClassString()})"
-          if dyn
-            java_throw @, 'java/lang/ClassNotFoundException', msg
-          else
-            java_throw @, 'java/lang/NoClassDefFoundError', msg
-        @loaded_classes[cls] = class_file
+        # a class gets loaded with the loader of the class that is triggering
+        # this class resolution
+        defining_class = @curr_frame().method.class_type.toClassString()
+        defining_class_loader = @class_states[defining_class]?.loader
+        if defining_class_loader?
+          @meta_stack().push root.StackFrame.fake_frame('custom_class_loader')
+          @push2 defining_class_loader, @init_string util.ext_classname cls
+          @method_lookup(
+            class: defining_class_loader.type.toClassString()
+            sig: 'loadClass(Ljava/lang/String;)Ljava/lang/Class;').setup_stack @
+          unless @run_until_finished (->), (->), true
+            throw 'Error in class initialization'
+          # discard return value. @define_class will have registered the new
+          # file in loaded_classes for us.
+          @meta_stack().pop()
+        else
+          # bootstrap class loader
+          @class_states[cls] = new ClassState null
+          class_file = @read_classfile cls
+          if not class_file? or wrong_name = (class_file.this_class.toClassString() != cls)
+            msg = cls
+            if wrong_name
+              msg += " (wrong name: #{class_file.this_class.toClassString()})"
+            if dyn
+              java_throw @, 'java/lang/ClassNotFoundException', msg
+            else
+              java_throw @, 'java/lang/NoClassDefFoundError', msg
+          @loaded_classes[cls] = class_file
     @loaded_classes[cls]

   # Loads and initializes :type, and returns a ClassFile object. Should only be


:memo: Remove otherwise in param doc

:memo: Remove otherwise in param doc

--- src/scanner.coffee
+++ src/scanner.coffee
@@ -60,7 +60,7 @@
   # * stack: An array of {Rule} objects.
   # * line: The string being scanned.
   # * rule: The rule that matched.
-  # * endPatternMatch: true if the rule's end pattern matched, false otherwise.
+  # * endPatternMatch: true if the rule's end pattern matched.
   #
   # Returns an array of tokens representing the match.
   handleMatch: (match, stack, line, rule, endPatternMatch) ->


Make methods in class Mock private, move publish.mock into class Mock.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -1,3 +1,4 @@
+# TODO: or is it: public = exports ? this (http://stackoverflow.com/questions/4214731/coffeescript-global-variables/4215132#4215132)
 publish = exports ? window

@@ -8,33 +9,31 @@
 #   mocked_book.expects("read")
 #   ...
 #
-publish.mock = (test_function) ->
-  try
-    Mock.install_expects_method()
-    convenience_mocks = Mock.build_convenience_mock_objects()
-    Mock.run_test_function(test_function, convenience_mocks)
-    AllExpectations.verify_all_expectations()
-  finally
-    Mock.uninstall_expects_method()
-    AllExpectations.uninstall_all_mocked_methods()
-    AllExpectations.unregister_all_expectations()
-
-
-#
-# Mock
-#
 class Mock

-  @install_expects_method: ->
+  publish.mock = (test_function) ->
+    try
+      _install_expects_method()
+      convenience_mocks = _build_convenience_mock_objects()
+      _run_test_function(test_function, convenience_mocks)
+      AllExpectations.verify_all_expectations()
+    finally
+      _uninstall_expects_method()
+      AllExpectations.uninstall_all_mocked_methods()
+      AllExpectations.unregister_all_expectations()
+
+  # private
+
+  _install_expects_method = ->
     Object.prototype.expects = Expects.expects

-  @uninstall_expects_method: ->
+  _uninstall_expects_method = ->
     delete Object.prototype.expects

-  @build_convenience_mock_objects: ->
+  _build_convenience_mock_objects = ->
     ( new Object() for i in [1..5] )

-  @run_test_function: (test_function, convenience_mocks) ->
+  _run_test_function = (test_function, convenience_mocks) ->
     test_function.apply(undefined, convenience_mocks)


Remove config.bundledPackagesDirPath that was src/packages

--- src/app/config.coffee
+++ src/app/config.coffee
@@ -8,7 +8,6 @@
 pathWatcher = require 'pathwatcher'

 configDirPath = fsUtils.absolute("~/.atom")
-bundledPackagesDirPath = path.join(resourcePath, "src/packages")
 nodeModulesDirPath = path.join(resourcePath, "node_modules")
 bundledThemesDirPath = path.join(resourcePath, "themes")
 userThemesDirPath = path.join(configDirPath, "themes")
@@ -23,10 +22,9 @@
 class Config
   configDirPath: configDirPath
   themeDirPaths: [userThemesDirPath, bundledThemesDirPath]
-  bundledPackageDirPaths: [bundledPackagesDirPath, nodeModulesDirPath]
-  bundledPackagesDirPath: bundledPackagesDirPath
+  bundledPackageDirPaths: [nodeModulesDirPath]
   nodeModulesDirPath: nodeModulesDirPath
-  packageDirPaths: [userPackagesDirPath, bundledPackagesDirPath]
+  packageDirPaths: [userPackagesDirPath]
   userPackagesDirPath: userPackagesDirPath
   userStoragePath: userStoragePath
   lessSearchPaths: [path.join(resourcePath, 'static'), path.join(resourcePath, 'vendor')]


Update scroll position after layer dimensions

Calling updateLayerDimensions() may change the scroll position
so restore the edit session's stored scroll positions after
updateLayerDimensions() is called but store the scroll left and
scroll top positions beforehand.

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -846,10 +846,6 @@
         else
           element.removeClass('selected')

-  setScrollPositionFromActiveEditSession: ->
-    @scrollTop(@activeEditSession.scrollTop ? 0)
-    @scrollLeft(@activeEditSession.scrollLeft ? 0)
-
   saveScrollPositionForActiveEditSession: ->
     if @attached
       @activeEditSession.setScrollTop(@scrollTop())
@@ -1093,8 +1089,11 @@

     @clearRenderedLines()
     @removeAllCursorAndSelectionViews()
+    editSessionScrollTop = @activeEditSession.scrollTop ? 0
+    editSessionScrollLeft = @activeEditSession.scrollLeft ? 0
     @updateLayerDimensions()
-    @setScrollPositionFromActiveEditSession()
+    @scrollTop(editSessionScrollTop)
+    @scrollLeft(editSessionScrollLeft)
     @newCursors = @activeEditSession.getCursors()
     @newSelections = @activeEditSession.getSelections()
     @updateDisplay(suppressAutoScroll: true)


Fix the usage of spawn.

Fix the usage of spawn.

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -80,7 +80,7 @@
   bundledCommandPath = fs.resolve(window.resourcePath, 'atom.sh')
   if bundledCommandPath?
     fs.write(commandPath, fs.read(bundledCommandPath))
-    spawn("chmod u+x '#{commandPath}'")
+    spawn('chmod', ['u+x', commandPath])

 window.handleWindowEvents = ->
   $(window).command 'window:toggle-full-screen', => atom.toggleFullScreen()


:lipstick:

:lipstick:

--- src/app/cursor.coffee
+++ src/app/cursor.coffee
@@ -155,16 +155,16 @@
     allowPrevious = options.allowPrevious ? true
     currentBufferPosition = @getBufferPosition()
     previousNonBlankRow = @editSession.buffer.previousNonBlankRow(currentBufferPosition.row)
-    previousLinesRange = [[previousNonBlankRow, 0], currentBufferPosition]
-
-    beginningOfWordPosition = currentBufferPosition
-
-    @editSession.backwardsScanInRange (options.wordRegex ? @wordRegExp()), previousLinesRange, (match, matchRange, { stop }) =>
+    range = [[previousNonBlankRow, 0], currentBufferPosition]
+
+    beginningOfWordPosition = null
+    @editSession.backwardsScanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) =>
       if matchRange.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
         beginningOfWordPosition = matchRange.start
-      stop() unless beginningOfWordPosition.isEqual(currentBufferPosition)
-
-    beginningOfWordPosition
+      if not beginningOfWordPosition?.isEqual(currentBufferPosition)
+        stop()
+
+    beginningOfWordPosition or currentBufferPosition

   getEndOfCurrentWordBufferPosition: (options = {}) ->
     allowNext = options.allowNext ? true
@@ -172,14 +172,13 @@
     range = [currentBufferPosition, @editSession.getEofBufferPosition()]

     endOfWordPosition = null
-    @editSession.scanInRange (options.wordRegex ? @wordRegExp()),
-    range, (match, matchRange, { stop }) =>
+    @editSession.scanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) =>
       endOfWordPosition = matchRange.end
-      return if endOfWordPosition.isEqual(currentBufferPosition)
-
-      if not allowNext and matchRange.start.isGreaterThan(currentBufferPosition)
+      if matchRange.start.isGreaterThan(currentBufferPosition) and not allowNext
         endOfWordPosition = currentBufferPosition
-      stop()
+      if not endOfWordPosition.isEqual(currentBufferPosition)
+        stop()
+
     endOfWordPosition or currentBufferPosition

   getCurrentWordBufferRange: (options={}) ->


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -744,7 +744,7 @@

   renderLines: ->
     @clearRenderedLines()
-    @updateRenderedLines(reset: true)
+    @updateRenderedLines()

   clearRenderedLines: ->
     @lineCache = []


Restore reverted rename. clearLines -> clearRenderedLines

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -233,7 +233,7 @@
   afterAttach: (onDom) ->
     return if @attached or not onDom
     @attached = true
-    @clearLines()
+    @clearRenderedLines()
     @subscribeToFontSize()
     @calculateDimensions()
     @setMaxLineLength() if @softWrap
@@ -291,10 +291,10 @@
       @scrollTop() + @scrollView.height()

   renderVisibleLines: ->
-    @clearLines()
+    @clearRenderedLines()
     @updateVisibleLines()

-  clearLines: ->
+  clearRenderedLines: ->
     @lineCache = []
     @renderedLines.find('.line').remove()


Grouped context menu items into submenu

Grouped context menu items into submenu

--- menus/tabs-to-spaces.cson
+++ menus/tabs-to-spaces.cson
@@ -1,8 +1,13 @@
 'context-menu':
   'atom-text-editor, .overlayer': [
-    { label: 'Tabify', command: 'tabs-to-spaces:tabify' }
-    { label: 'Untabify', command: 'tabs-to-spaces:untabify' }
-    { label: 'Untabify All', command: 'tabs-to-spaces:untabify-all' }
+    {
+      'label': 'Tabs to spaces'
+      'submenu': [
+        { label: 'Tabify', command: 'tabs-to-spaces:tabify' }
+        { label: 'Untabify', command: 'tabs-to-spaces:untabify' }
+        { label: 'Untabify All', command: 'tabs-to-spaces:untabify-all' }
+      ]
+    }
   ]

 'menu': [


remove html convers

remove html convers

--- src/base_model.coffee
+++ src/base_model.coffee
@@ -13,16 +13,11 @@
 #
 #   class MyModel extends Sirius.BaseModel
 #     @attrs: ["id", {title: "default title"}, "description"]
-#     @to:
-#       id          : tag: "b", class: 'my-model-id'
-#       title       : tag: "span", class: "my-model-title"
 #
 #   class Person extends BaseModel
 #     @attrs: ["id", "name"]
 #
 #     @has_many: [Group]
-#
-#     @form_name: "person-model"
 #
 #     @guid_for: id
 #
@@ -33,11 +28,6 @@
 #       name:
 #         presence: true
 #         format: with: /^[A-Z].+/
-#
-#     @to:
-#       id          : tag: "b", class: 'person-id'
-#       name        : tag: "span", class: "person-name"
-#       group       : tag: "p", class: "group"
 #
 #     to_string: () ->
 #       "name: #{name}; id: #{id}, group count: #{@get('group').length}"
@@ -110,27 +100,6 @@
        group.get('person_id') // => 1
   ###
   @belongs_to: []
-
-  ###
-    object, which contain attributes names as keys, and values is a object, with properties for html element
-    @note when not set a `tag` for `attribute` default `tag` is a `div`
-    @example
-       class Person extends Sirius.BaseModel
-         @attrs: ["id", "name"]
-         @to :
-           id : tag: b, class: 'person-class'
-      (new Person({id: 1, name: "Abc"})).to_html() # => 1
Abc

-  ###
-  @to: {}
-
-  ###
-    name for element, when you want convert html to Model
-    @note when it not define, then it's create from model name as: ModelName => model_name
-    @example
-       class Person extends
-         @form_name: 'a-b-c'
-  ###
-  @form_name: null

   ###
     attribute name, for which generate guid
@@ -444,45 +413,6 @@
       JSON.stringify(o)
     else
       JSON.stringify(z)
-
-  #
-  # Convert model into array of element instances
-  # @return [String]
-  #
-  # @example
-  #   var m = new MyModel({"id": 10, "title": "my title", "description": "text..."});
-  #   m.to_html() // => "10my title
text...
"
-  #
-  #   person.to_html() // =>
-  #   // "1
-  #   //  Abc
-  #   //

-  #   //      group-0
-  #   //
1

-  #   //      group-1
-  #   //
1

-  #   //

-  #
-  to_html: () ->
-    to = @constructor.to || {}
-
-    result = for key in @attributes
-      value = @get(key)
-      obj   = to[key]    || {}
-      tag   = obj["tag"] || "div"
-      attr  = for k, v of obj when k isnt "tag" then "#{k} = '#{v}'"
-      attr = if attr.length == 0 then "" else " #{attr.join(' ')}"
-
-      value = if @has_many().indexOf(key) > -1
-        for v in value then v.to_html()
-      else if @has_one().indexOf(key) > -1
-        value.to_html()
-      else
-        value
-
-      "<#{tag}#{attr}>#{value}</#{tag}>"
-
-    result.join("")

   # Create a new model instance from json structure.
   # @param json [JSON] - json object
@@ -529,32 +459,6 @@
           json[attr]
         m.set(attr, value || m.get(attr))
     m
-
-  #
-  # Generate a new model instance from form
-  # @param selector [String] - element selector, for find element, where model may be present in html
-  # @return [T < Sirius.BaseModel]
-  #
-  # @example
-  #   //html form
-  #   <form name="my_model" id="my-model-form">
-  #     <input type="hidden" name="id" class="my-id" value="1" />
-  #     <input type="text" name="title" class="title" value="new title" />
-  #     <textarea name="description">text...</textarea>
-  #   </form>
-  #
-  #   var m = MyModel.from_html("#my-model-form");
-  #   m.get("id")          // => 1
-  #   m.get("title")       // => new title
-  #   m.get("description") // => text...
-  #
-  #
-  @from_html: (selector) ->
-    #FIXME
-    form_name = selector || @.form_name || Sirius.Utils.underscore(@.name)
-
-    @.from_json(Sirius.Application.adapter.form_to_json(form_name))
-

   # usage for comparing models
   # When equal return true, otherwise return false


make room name content editable

make room name content editable

--- client/app/scripts/users.coffee
+++ client/app/scripts/users.coffee
@@ -16,11 +16,23 @@
       input className: "credentials-send", type: "submit", value: "Send",

 usersHeader = React.createClass
+  handleRoomContentEditable: () ->
+    node = @refs.roomName.getDOMNode()
+    node.textContent = ""
+    node.contentEditable = true
+    node.focus()
+
+  changeRoom: (ev) ->
+    if ev.keyCode isnt 13 then return
+    node = @refs.roomName.getDOMNode()
+    node.contentEditable = false
+    node.blur()
+
   render: ->
     header className: "users-header",
       div {},
-        span className: "room-name", @props.room
-        i className: "fa fa-times"
+        span {className: "room-name", ref: "roomName", onBlur: @changeRoom, onKeyDown: @changeRoom}, @props.room
+        i className: "fa fa-times", onClick: @handleRoomContentEditable

 usersContainer = React.createClass
   render: ->


Fix fullscreen keybinding prefix

Fix fullscreen keybinding prefix

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -60,7 +60,7 @@
       { command: 'go to tab number',    binding: 'mod+7',      global: yes }
       { command: 'go to tab number',    binding: 'mod+8',      global: yes }
       { command: 'go to tab number',    binding: 'mod+9',      global: yes }
-      { command: 'fullscren ideview',   binding: 'meta+shift+enter', global: yes }
+      { command: 'fullscren ideview',   binding: 'mod+shift+enter', global: yes }
     ]

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


Print stack trace (if present)

Print stack trace (if present)

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -222,6 +222,7 @@
               console.log result
               process.exit 0
             .catch (err) ->
+              if err.stack then console.error(err.stack)
               console.error err
               process.exit 1
             .done()
@@ -295,6 +296,7 @@
               console.log result
               process.exit 0
             .catch (err) ->
+              if err.stack then console.error(err.stack)
               console.error err
               process.exit 1
             .done()
@@ -306,6 +308,7 @@
                 console.log result
                 process.exit 0
               .catch (err) ->
+                if err.stack then console.error(err.stack)
                 console.error err
                 process.exit 1
             .catch (err) ->


Whitespace clean-up.

Whitespace clean-up.

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -371,8 +371,6 @@
         return !!(t for t in tokens when t and t[0] == '.').length

-
-
 # A class that performs static analysis of the abstract
 # syntax tree.
 class ASTLinter


lastFM has changed the way it returns the currently playing song :sweat:

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -54,8 +54,9 @@
     @_get url, (response) ->
       response = JSON.parse(response.response).recenttracks
       if response.track? and isArray response.track
-        callback? response.track[0]
-      else callback?()
+        track = response.track[0]
+        if track["@attr"]?.nowplaying is "true" then return callback? track
+      return callback?()

 window.aberigle = aberigle = {}
 aberigle.api =
@@ -69,12 +70,14 @@

   drawTrack = (track) ->
     unless track? then return false
+    imageUrl = track.image[track.image.length - 1]["#text"]
+    if imageUrl is "" then return false
+    background.style.backgroundImage = "url(#{imageUrl})"
+
     link = copyright.children[1]
     link.text = track.name + " - " + track.artist["#text"]
     link.href = "http://www.last.fm/user/Jayle23"
-    imageUrl = track.image[track.image.length - 1]["#text"]
-    if imageUrl is "" then return false
-    background.style.backgroundImage = "url(#{imageUrl})"
+
     equalizer = copyright.children[0]
     equalizer.src = './static/images/equalizer.gif'

@@ -92,5 +95,4 @@
     link.text = response.copyright

   aberigle.api.lastfm.getNowPlaying "jayle23", (track) ->
-    unless drawTrack(track)
-      aberigle.api.image.getImage drawImage
+    unless drawTrack(track) then aberigle.api.image.getImage drawImage


Add option to save csv using specific path and file name.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -16,6 +16,7 @@
   .option('excludeEmptyStocks', 'whether to skip empty stocks or not')
   .describe('channelKey', 'when you want to filter stock entries for a special channel key')
   .describe('targetDir', 'the folder where exported files are saved')
+  .describe('targetFile', 'the file where exported stocks are saved')
   .describe('useExportTmpDir', 'whether to use a system tmp folder to store exported files')
   .describe('logLevel', 'log level for file logging')
   .describe('logDir', 'directory to store logs')
@@ -68,7 +69,7 @@
       csvMapper.mapStocks(result)
       .then (data) ->
         ts = (new Date()).getTime()
-        csvFile = "#{@outputDir}/stocks_#{ts}.csv"
+        csvFile = argv.targetFile or "#{@outputDir}/stocks_#{ts}.csv"
         logger.info "Storing CSV export to '#{csvFile}'."
         fs.writeFileAsync csvFile, data
 .catch (err) ->


emit second-semester specific view

emit second-semester specific view

--- coach/src/navigation/index.cjsx
+++ coach/src/navigation/index.cjsx
@@ -39,8 +39,7 @@
     channel.emit('show.student_id', view: 'student_id')

   onCourseEnded: ->
-    # FIXME: this should go to new registration page
-    channel.emit('show.registration', view: 'registration')
+    channel.emit('show.registration', view: 'second-semester-registration')

   render: ->
     {course} = @props


choose whether to scroll blocks or text to end based on current mode

--- src/controller.coffee
+++ src/controller.coffee
@@ -2869,12 +2869,14 @@
 # Moves the cursor to the end of the document and scrolls it into position
 # (in block and text mode)
 Editor::scrollCursorToEndOfDocument = ->
-  pos = @tree.end
-  while pos && !@validCursorPosition(pos)
-    pos = pos.prev
-  @setCursor(pos)
-  @scrollCursorIntoPosition()
-  @aceEditor.scrollToLine @aceEditor.session.getLength()
+  if @currentlyUsingBlocks
+    pos = @tree.end
+    while pos && !@validCursorPosition(pos)
+      pos = pos.prev
+    @setCursor(pos)
+    @scrollCursorIntoPosition()
+  else
+    @aceEditor.scrollToLine @aceEditor.session.getLength()

 # Pressing the up-arrow moves the cursor up.


Allow accepting ace editor as construction argument

--- src/controller.coffee
+++ src/controller.coffee
@@ -174,7 +174,7 @@

 # ## The Editor Class
 exports.Editor = class Editor
-  constructor: (@wrapperElement, @options) ->
+  constructor: (@aceEditor, @options) ->
     # ## DOM Population
     # This stage of ICE Editor construction populates the given wrapper
     # element with all the necessary ICE editor components.
@@ -188,11 +188,6 @@

     # We give our element a tabIndex so that it can be focused and capture keypresses.
     @dropletElement.tabIndex = 0
-
-    # Append that div.
-    @wrapperElement.appendChild @dropletElement
-
-    @wrapperElement.style.backgroundColor = '#FFF'

     # ### Canvases
     # Create the palette and main canvases
@@ -230,8 +225,6 @@
     @paletteWrapper.style.width = '270px'

     @dropletElement.style.left = @paletteWrapper.offsetWidth + 'px'
-
-    @wrapperElement.appendChild @paletteWrapper

     @draw = new draw.Draw()

@@ -255,19 +248,37 @@
       ctx: @mainCtx
       draw: @draw

-    @aceElement = document.createElement 'div'
-    @aceElement.className = 'droplet-ace'
-
-    @wrapperElement.appendChild @aceElement
-
-    @aceEditor = ace.edit @aceElement
-
-    @aceEditor.setTheme 'ace/theme/chrome'
-    @aceEditor.setFontSize 15
-    acemode = @options.mode
-    if acemode is 'coffeescript' then acemode = 'coffee'
-    @aceEditor.getSession().setMode 'ace/mode/' + acemode
-    @aceEditor.getSession().setTabSize 2
+    # We can be passed a div
+    if @aceEditor instanceof Node
+      @wrapperElement = @aceEditor
+
+
+      @aceElement = document.createElement 'div'
+      @aceElement.className = 'droplet-ace'
+
+      @wrapperElement.appendChild @aceElement
+
+      @aceEditor = ace.edit @aceElement
+
+      @aceEditor.setTheme 'ace/theme/chrome'
+      @aceEditor.setFontSize 15
+      acemode = @options.mode
+      if acemode is 'coffeescript' then acemode = 'coffee'
+      @aceEditor.getSession().setMode 'ace/mode/' + acemode
+      @aceEditor.getSession().setTabSize 2
+
+    else
+      @wrapperElement = document.createElement 'div'
+      @wrapperElement.className = 'droplet-editor'
+
+      @aceEditor.container.parentElement.appendChild @wrapperElement
+      @wrapperElement.appendChild @aceEditor.container
+
+    # Append populated divs
+    @wrapperElement.appendChild @dropletElement
+    @wrapperElement.appendChild @paletteWrapper
+
+    @wrapperElement.style.backgroundColor = '#FFF'

     @currentlyAnimating = false


Fix bugs with undoing cut/paste

Fix bugs with undoing cut/paste

--- src/controller.coffee
+++ src/controller.coffee
@@ -708,6 +708,11 @@
       # Move a clone into position.
       (clone = @block.clone()).moveTo editor.tree.getTokenAtLocation @before

+      # If the block was the lasso select, register it
+      # as such.
+      if @block.type is 'segment' and @block.isLassoSegment
+        editor.lassoSegment = @block
+
       # Move the cursor to the end of it.
       return clone.end

@@ -1960,7 +1965,7 @@
     constructor: (segment) ->
       @first = segment.start.getSerializedLocation()
       @last = segment.end.getSerializedLocation()
-      @lassoSelect = segment.isLassoSelect
+      @lassoSelect = segment.isLassoSegment

     undo: (editor) ->
       editor.tree.getTokenAtLocation(@first).container.unwrap()
@@ -1969,9 +1974,9 @@

     redo: (editor) ->
       segment = new model.Segment()
-      segment.lassoSelect = @lassoSelect
-      editor.tree.getTokenAtLocation(@first).insertBefore segment.start
-      editor.tree.getTokenAtLocation(@last).insertBefore segment.end
+      segment.isLassoSegment = @lassoSelect
+      segment.wrap editor.tree.getTokenAtLocation(@first),
+        editor.tree.getTokenAtLocation(@last)

       return segment.end

@@ -1979,13 +1984,16 @@
     constructor: (segment) ->
       @first = segment.start.getSerializedLocation()
       @last = segment.end.getSerializedLocation()
-      @lassoSelect = segment.isLassoSelect
+      @lassoSelect = segment.isLassoSegment

     undo: (editor) ->
       segment = new model.Segment()
-      segment.lassoSelect = @lassoSelect
-      editor.tree.getTokenAtLocation(@first).insertBefore segment.start
-      editor.tree.getTokenAtLocation(@last).insertBefore segment.end
+      segment.isLassoSegment = @lassoSelect
+      segment.wrap editor.tree.getTokenAtLocation(@first),
+        editor.tree.getTokenAtLocation(@last)
+
+      if @lassoSelect
+        editor.lassoSegment = segment

       return segment.end

@@ -2324,7 +2332,6 @@
           persistentParent = @textFocus.parent.parent

           chars = getCharactersTo persistentParent, head.container.start
-          console.log 'chars:', chars
           @setTextInputFocus null
           socket = getSocketAtChar persistentParent, chars
         else
@@ -2651,8 +2658,6 @@

     @wrapperElement.appendChild @aceElement

-    console.log 'Just appended ace element'
-
     @aceEditor = ace.edit @aceElement

     @aceEditor.setTheme 'ace/theme/chrome'
@@ -3772,14 +3777,19 @@
         @iceElement.focus()

     pressedVKey = false
+    pressedXKey = false

     @copyPasteInput.addEventListener 'keydown', (event) ->
       if event.keyCode is 86
         pressedVKey = true
+      else if event.keyCode is 88
+        pressedXKey = true

     @copyPasteInput.addEventListener 'keyup', (event) ->
       if event.keyCode is 86
         pressedVKey = false
+      else if event.keyCode is 88
+        pressedXKey = false

     @copyPasteInput.addEventListener 'input', =>
       if pressedVKey
@@ -3796,6 +3806,10 @@
           blocks = coffee.parse str

           @addMicroUndoOperation 'CAPTURE_POINT'
+          if @lassoSegment?
+            @addMicroUndoOperation new PickUpOperation @lassoSegment
+            @lassoSegment.spliceOut(); @lassoSegment = null
+
           @addMicroUndoOperation new DropOperation blocks, @cursor.previousVisibleToken()

           blocks.spliceIn @cursor
@@ -3810,6 +3824,11 @@
           @redrawMain()

         @copyPasteInput.setSelectionRange 0, @copyPasteInput.value.length
+      else if pressedXKey and @lassoSegment?
+        @addMicroUndoOperation 'CAPTURE_POINT'
+        @addMicroUndoOperation new PickUpOperation @lassoSegment
+        @lassoSegment.spliceOut(); @lassoSegment = null
+        @redrawMain()

   hook 'populate', 0, ->
     setTimeout (=>


Fix bug with floating blocks and remembered sockets. TODO write a test

--- src/controller.coffee
+++ src/controller.coffee
@@ -1614,11 +1614,12 @@

     @setCursor @draggingBlock.start

+    # TODO write a test for this logic
     for el, i in rememberedSocketOffsets
       @rememberedSockets.push new RememberedSocketRecord(
         new CrossDocumentLocation(
-          @floatingBlocks.length - 1,
-          new model.Location(el.offset, 'socket')
+          @floatingBlocks.length,
+          new model.Location(el.offset + 1, 'socket')
         ),
         el.text
       )


Bind to tab instead of arrow keys for socket movement

--- src/controller.coffee
+++ src/controller.coffee
@@ -1943,12 +1943,9 @@
     @moveCursorTo @cursor.next.next
     @scrollCursorIntoPosition()

-  hook 'key.left', 0, ->
+  hook 'key.shift tab', 0, ->
     if @socketFocus?
-      if @hiddenInput.selectionEnd is 0
-        head = @socketFocus.start
-
-      else return
+      head = @socketFocus.start

     else
       head = @cursor
@@ -1959,12 +1956,11 @@
     if head?
       @setTextInputFocus head.container, -1, -1

-  hook 'key.right', 0, ->
+    return false
+
+  hook 'key.tab', 0, ->
     if @socketFocus?
-      if @hiddenInput.selectionEnd is @hiddenInput.value.length
-        head = @socketFocus.end
-
-      else return
+      head = @socketFocus.end

     else
       head = @cursor
@@ -1974,6 +1970,8 @@

     if head?
       @setTextInputFocus head.container
+
+    return false

   Editor::deleteAtCursor = ->
     # Unfocus any inputs, which could get in the way.


add documentation

add documentation

--- src/currencies.coffee
+++ src/currencies.coffee
@@ -1,3 +1,12 @@
+# Description:
+#   allows hubot to perform currency exchange in 33 currencies. Uses open ECB data.
+#
+# Commands:
+#   hubot convert <money> <currency code> into <currency code> - converts currency
+#
+# Author:
+#   sighmoan
+
 currencies = [
   "EUR",
   "USD",


Give packages dir precedence to env var over internal config.

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -77,8 +77,8 @@
     path.join(path.dirname(packagePath), packageName)

   getPackagesDirectory: ->
-    atom.config.get('core.projectHome') or
-      process.env.ATOM_REPOS_HOME or
+    process.env.ATOM_REPOS_HOME or
+      atom.config.get('core.projectHome') or
       path.join(fs.getHomeDirectory(), 'github')

   validPackagePath: ->


start integration specs refactoring

start integration specs refactoring

--- test/integrationSpec.coffee
+++ test/integrationSpec.coffee
@@ -1,12 +1,45 @@
 define ['main'], (JasmineSugar) ->
-  describe 'integration tests with JasmineEmulator(JE)', ->
-    context = undefined
+  fdescribe 'integration tests with JasmineEmulator(JE)', ->
+    context                 =
+    collectionInIt          =
+    collectionInDescribe    =
+    collectionOutOfDescribe = undefined

     beforeEach ->
       Jasmine = getEnv: -> JE
       context = jasmine: Jasmine

       JasmineSugar.setup(context)
+
+      context.describe 'test', ->
+        collection.is 'something'
+
+        @it -> # using JasmineSugar it method ;)
+          collectionInIt = collection
+        collectionInDescribe = collection
+
+      collectionOutOfDescribe = collection if collection?
+
+      JE.run(context)
+
+      collectionOutOfTestCase = collection if collection?
+
+    describe 'collection in it block', ->
+      it 'should be defined', ->
+        expect(collectionInIt).toBeDefined()
+
+      it 'should be eq something', ->
+        expect(collectionInIt).toEqual 'something'
+
+    describe 'collection in describe block', ->
+      it 'should be defined', ->
+        expect(collectionInDescribe).toBeDefined()
+
+      it 'should be DSL object', ->
+        expect(collectionInDescribe).toHaveProperties ['is']
+
+    describe 'collection in outer context', ->
+      it ''

     it 'with plain type one declaration', ->
       context.describe 'test', ->


context: isolate registry

context: isolate registry

--- test/options/handler.coffee
+++ test/options/handler.coffee
@@ -60,7 +60,7 @@

     it 'passed as argument', (next) ->
       mecano()
-      .register 'anaction', (options, callback) ->
+      .registry.register 'anaction', (options, callback) ->
         process.nextTick -> callback Error 'Catchme'
       .anaction
         key: "value"


making player sprite change depending on gender

--- src/coffee/controllers/player/player-map-ctrl.coffee
+++ src/coffee/controllers/player/player-map-ctrl.coffee
@@ -86,7 +86,12 @@

           handleObjects()

-          sprite = @game.add.sprite player.x*16, player.y*16, 'interactables', 12
+          sprite = @game.add.sprite player.x*16, player.y*16, 'interactables',
+            switch player.gender
+              when 'male' then 12
+              when 'female' then 13
+              else 11
+
           @game.camera.follow sprite

           text = @game.add.text 10, 10, (textForPlayer player), {font: '15px Arial', fill: '#fff', stroke: '#000', strokeThickness: 3}


Fixing test to check loading local images

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -129,7 +129,7 @@
 			layer._element.style.webkitTransformStyle.should.equal "flat"

-		it "should set local image", ->
+		it "should set local image", (done) ->

 			prefix = "../"
 			imagePath = "static/test.png"
@@ -139,15 +139,17 @@
 			layer.image = fullPath
 			layer.image.should.equal fullPath

-			layer.style["background-image"].indexOf(imagePath).should.not.equal(-1)
-			layer.style["background-image"].indexOf("file://").should.not.equal(-1)
-			layer.style["background-image"].indexOf("?nocache=").should.not.equal(-1)
-
+			image = layer.props.image
+			layer.props.image.should.equal fullPath
+
+			layer.on Events.ImageLoaded, ->
+				layer.style["background-image"].indexOf(imagePath).should.not.equal(-1)
+				layer.style["background-image"].indexOf("file://").should.not.equal(-1)
+				layer.style["background-image"].indexOf("?nocache=").should.not.equal(-1)
+				done()
+
 			#layer.computedStyle()["background-size"].should.equal "cover"
 			#layer.computedStyle()["background-repeat"].should.equal "no-repeat"
-
-			image = layer.props.image
-			layer.props.image.should.equal fullPath

 		it "should set image", ->
 			imagePath = "../static/test.png"


Test Delete and Query

Test Delete and Query

--- test/unit/model_spec.coffee
+++ test/unit/model_spec.coffee
@@ -51,11 +51,19 @@
         [200, message, {}]

       $httpBackend.whenPOST(messageDetailUrl).respond (method, url, data) ->
-        id = messageDetailUrl.exec(url)[1]
+        id = +messageDetailUrl.exec(url)[1]
         log "POST /messages/#{id}"
         message = _.findWhere messages, id: id
         message = data
         [200, message, {}]
+
+      $httpBackend.whenDELETE(messageDetailUrl).respond (method, url, data) ->
+        id = +messageDetailUrl.exec(url)[1]
+        log "DELETE /messages/#{id}"
+        message = _.findWhere messages, id: id
+        recover = _.cloneDeep(message)
+        delete messages[messages.indexOf(message)]
+        [200, recover, {}]

     # ### Initialize new Collection each time
     Messages = null
@@ -137,6 +145,34 @@
       .then null, err

       tick()
+
+    # ### DELETE /messages/2
+    it 'should delete an entry', (done) ->
+      query = id: 2
+      Messages.destroy(query)
+      .then ->
+        messages = Messages.where query
+        expect(messages).to.be.an('array')
+        expect(messages.length).to.eql 0
+        done(null)
+      .then null, (err) ->
+        done new Error JSON.stringify err
+
+      tick()
+
+    # ### Query cached data
+    it 'should query cached data', (done) ->
+      Messages.all().$promise
+      .then ->
+        messages = Messages.where subject: 'Hello World'
+        expect(messages).to.be.an('array')
+        expect(messages.length).to.eql 1
+        done(null)
+      .then null, (err) ->
+        done new Error JSON.stringify err
+
+      tick()
+

   # ## Singleton Objects
   describe "Singleton Resource", ->


fixed automation script when image type is missing

--- plugins/compute/app/assets/javascripts/compute/cloud_init_configuration.coffee
+++ plugins/compute/app/assets/javascripts/compute/cloud_init_configuration.coffee
@@ -2,43 +2,98 @@
   event.preventDefault()
   event.stopPropagation()
   button = $(event.target)
-  action = $(event.target).data('automationScriptAction')
-  icon = $(event.target).find('i.fa-plus')
+  action_path = $(event.target).data('automationScriptAction')
+  os_image_option = $('#server_image_id option:selected')
+  checkOsType(button, os_image_option, action_path)
+
+@fetchLinuxScritp=(event) ->
+  event.preventDefault()
+  event.stopPropagation()
+  button = $(event.target)
+  action_path = button.data('automationScriptAction')
+  $('a[data-toggle="windowsAutomationScript"]').addClass('disabled')
+  startSpinner(button)
+  fetchAutomationScript(button, 'linux', action_path)
+
+@fetchWindowsScritp=(event) ->
+  event.preventDefault()
+  event.stopPropagation()
+  button = $(event.target)
+  action_path = button.data('automationScriptAction')
+  $('a[data-toggle="linuxAutomationScript"]').addClass('disabled')
+  startSpinner(button)
+  fetchAutomationScript(button, 'windows', action_path)
+
+@startSpinner = (button) ->
+  icon = button.find('i.fa-plus')
+  spinner = button.find('i.loading-spinner-button')
   icon.addClass('hide')
-  spinner = $(event.target).find('i.loading-spinner-button')
   spinner.removeClass('hide')
-  os_image = $('#server_image_id option:selected').data('vmwareOstype')
+
+@stopSpinner = (button) ->
+  icon = button.find('i.fa-plus')
+  spinner = button.find('i.loading-spinner-button')
+  icon.removeClass('hide')
+  spinner.addClass('hide')
+
+@addEventListenerOnSelect= (button) ->
+  $( "#server_image_id" ).unbind "change.automtion"
+  $( "#server_image_id" ).bind "change.automtion", () -> removeOsTypeButtons()
+
+@checkOsType = (button, os_image_option, action_path) ->
+  os_image = os_image_option.data('vmwareOstype')
+
+  # check empty image
+  if os_image_option.val() == ""
+    attachPopover(button, 'Error', "Please choose an image.")
+    return
+  # check image
+  if os_image == "" || os_image == null || typeof os_image == "undefined"
+    attachPopover(button, 'Warning', "Missing property 'vmware_ostype' on the image provided. Please follow the steps described in the documentation to upload a compatible image. See customer images documentation. Please choose manually.")
+    addEventListenerOnSelect(button)
+    addOsTypeButtons(button, action_path)
+    return
+  # get script
+  $('a[data-toggle="windowsAutomationScript"]').addClass('disabled')
+  startSpinner(button)
+  fetchAutomationScript(button, os_image, action_path)
+
+@addOsTypeButtons = (button, action_path) ->
+  removeOsTypeButtons()
+  if button.attr('data-toggle') == 'automationBootstrap'
+    button.before( '' +
+                      '' +
+                      'Linux' +
+                      '' +
+                      'Windows' +
+                    '' );
+
+@removeOsTypeButtons = () ->
+  $('.osTypeOptionButtons').remove()
+
+@fetchAutomationScript = (button, os_image, action_path) ->
   osImageJSON = new Object()
   osImageJSON.vmwareOstype = os_image
-
   $.ajax
-    url: action,
+    url: action_path,
     method: 'POST'
     dataType: 'json'
     data: JSON.stringify(osImageJSON)
     success: ( data, textStatus, jqXHR ) ->
-      addScriptToUserAttributes(data.script, os_image)
+      addScriptToUserAttributes(data.script, button, os_image)
     error: (xhr, bleep, error) ->
       attachPopover(button, 'Error', 'Something went wrong while processing your request. Please try again later.')
     complete: () ->
-      # console.log("complete")
-      icon.removeClass('hide')
-      spinner.addClass('hide')
+      stopSpinner(button)

-@addScriptToUserAttributes = (script, os_image) ->
-  button = $('a[data-toggle="automationBootstrap"]')
-  if os_image == "" || os_image == null
-    attachPopover(button, 'Error', "Missing property 'vmware_ostype' on the image provided. Please follow the steps described in the documentation to upload a compatible image. See customer images documentation")
-    return
-  if typeof os_image == "undefined"
-    attachPopover(button, 'Error', "Please choose an image.")
-    return
+@addScriptToUserAttributes = (script, button, os_image) ->
   osTypeWindows = os_image.search("windows")
   userDataFieldText = $('#server_user_data').val()

   if !userDataFieldText.trim()
     # empty
     $('#server_user_data').val(script)
+    removeOsTypeButtons()
   else
     # not empty
     if osTypeWindows >= 0
@@ -48,14 +103,17 @@
       # linux
       if userDataFieldText.match("^#cloud-config")
         $('#server_user_data').val("#{$('#server_user_data').val()}\n\n#{script}")
+        removeOsTypeButtons()
       else
         attachPopover(button, 'Error', "This doesn't semm to be a valid cloud config. Cloud config files starts with #cloud-config")

 @attachPopover = (element, title, body) ->
+  element.find('.popover').remove()
   element.popover(
     title: title
     content: body
     html: true
+    placement: "top"
   )
   element.popover('show')
   element.off('blur').on 'blur', ->
@@ -63,3 +121,5 @@

 $ ->
   $(document).on 'click','a[data-toggle="automationBootstrap"]', automationBootstrap
+  $(document).on 'click','a[data-toggle="linuxAutomationScript"]', fetchLinuxScritp
+  $(document).on 'click','a[data-toggle="windowsAutomationScript"]', fetchWindowsScritp


Mobile phone connection states

Mobile phone connection states

--- src/public/scripts/app.game.coffee
+++ src/public/scripts/app.game.coffee
@@ -71,12 +71,15 @@
 							@inputHandler.selectInput('mobile')
 							# Should be called when a user decides to connect his mobile phone
 							@createControllerNode()
-							console.log '/controller/' + @controllerNode.id
-							@controllerNode.on('peer.added', ( peer ) =>
-									console.log 'CONNECTED WITH PHONE'
-								)
-							@controllerNode.on('joined', ( peer ) =>
-									console.log 'JUST TESTING'
+							@inputHandler.showLoadingScreen()
+							@controllerNode.server.on('connect', ( peer ) =>
+									@controllerNode.server.off('connect')
+									#getQRCode @controllerNode.id
+									@inputHandler.showMobileConnectScreen(@controllerNode.id)
+									@controllerNode.on('peer.added', ( peer ) =>
+											@controllerNode.off('peer.added')
+											@inputHandler.showInfoScreen(type)
+										)
 								)
 				)

@@ -119,11 +122,13 @@

 			@node.server.on('connect', ( ) =>
 				# now ready to spawn player
+				@node.server.off('connect')
 				@status = 1
 			)

 			@node.on('joined', =>
 				# now ready to broadcast player
+				@node.off('joined')
 				@welcomeScreen.showWelcomeScreen()
 				@status = 2
 			)


Enable spec.

Enable spec.

--- src/spec/exportmapping.spec.coffee
+++ src/spec/exportmapping.spec.coffee
@@ -67,9 +67,9 @@
       row = @exportMapping.mapVariant(variant)
       expect(row).toEqual [ 'bar' ]

-  xdescribe '#mapBaseProduct', ->
+  describe '#mapBaseProduct', ->
     it 'should map productType and id', ->
-      @exportMapping.header = new Header("#{CONS.HEADER_PRODUCT_TYPE},#{CONS.HEADER_ID}")
+      @exportMapping.header = new Header([CONS.HEADER_PRODUCT_TYPE,CONS.HEADER_ID])
       @exportMapping.header.toIndex()
       product =
         id: '123'


Removed 'application/xml' from Request Header

This is too fix issue #40

--- src/turbolinks/http_request.coffee
+++ src/turbolinks/http_request.coffee
@@ -63,7 +63,7 @@
     @xhr = new XMLHttpRequest
     @xhr.open("GET", @url, true)
     @xhr.timeout = @constructor.timeout * 1000
-    @xhr.setRequestHeader("Accept", "text/html, application/xhtml+xml, application/xml")
+    @xhr.setRequestHeader("Accept", "text/html, application/xhtml+xml")
     @xhr.setRequestHeader("Turbolinks-Referrer", @referrer)
     @xhr.onprogress = @requestProgressed
     @xhr.onload = @requestLoaded


open passphrase pop up on login

open passphrase pop up on login

--- web/js/config.coffee
+++ web/js/config.coffee
@@ -8,7 +8,7 @@
     $rootScope.$on 'event:auth-loginRequired', (e) ->
       LoginPop.open()

-    PassphraseService.init()
+    PassphraseService.open()

 ])


Optional SQL profiling.

Optional SQL profiling.

--- main.coffee
+++ main.coffee
@@ -20,41 +20,44 @@

 anyDB = require 'any-db'
 dbConnection = anyDB.createPool config.DATABASE_URL, min: 2, max: 20
-dbConnection._query = dbConnection.query
-dbConnection.query = (q, data, cb) ->
-	logged = q
-	for value, index in data
-		index++
-		while logged.indexOf("$#{index}") isnt -1
-			logged = logged.replace "$#{index}", "<#{value}>"
-	Date start = new Date()
-	@_query q, data, (error, result) ->
-		time = new Date() - start
-		console.log "\n#{time} ms: #{logged}\n"
-		cb(error, result)
-dbConnection._begin = dbConnection.begin
-dbConnection.begin = ->
-	console.log "\nBEGIN TRANSACTION\n"
-	tx = @_begin()
-	tx.____start = new Date()
-	tx.__query = tx.query
-	tx.query = (q, data, cb) ->
+
+# Attach profiler?
+if process.env.SQLPROF is 'true'
+	dbConnection._query = dbConnection.query
+	dbConnection.query = (q, data, cb) ->
 		logged = q
 		for value, index in data
 			index++
 			while logged.indexOf("$#{index}") isnt -1
 				logged = logged.replace "$#{index}", "<#{value}>"
 		Date start = new Date()
-		@__query q, data, (error, result) ->
+		@_query q, data, (error, result) ->
 			time = new Date() - start
-			console.log "\n#{time} ms: t: #{logged}\n"
+			console.log "\n#{time} ms: #{logged}\n"
 			cb(error, result)
-	# doesn't work
-	#tx.__commit = tx.commit
-	#tx.commit = (a) ->
-		#console.log "\nCOMMIT, the whole thing took #{new Date() - tx.____start} ms\n"
-		#tx.__commit a
-	return tx
+	dbConnection._begin = dbConnection.begin
+	dbConnection.begin = ->
+		console.log "\nBEGIN TRANSACTION\n"
+		tx = @_begin()
+		tx.____start = new Date()
+		tx.__query = tx.query
+		tx.query = (q, data, cb) ->
+			logged = q
+			for value, index in data
+				index++
+				while logged.indexOf("$#{index}") isnt -1
+					logged = logged.replace "$#{index}", "<#{value}>"
+			Date start = new Date()
+			@__query q, data, (error, result) ->
+				time = new Date() - start
+				console.log "\n#{time} ms: t: #{logged}\n"
+				cb(error, result)
+		# doesn't work
+		#tx.__commit = tx.commit
+		#tx.commit = (a) ->
+			#console.log "\nCOMMIT, the whole thing took #{new Date() - tx.____start} ms\n"
+			#tx.__commit a
+		return tx

 lib = require './lib.coffee'
 async = require 'async'


Widgets from gist.

Widgets from gist.

--- main.coffee
+++ main.coffee
@@ -153,10 +153,9 @@

 ## From GUI

-# {"id":"A","data":[[1,2],[3,4]],"compute":true}
-
 # sheets

+###
 $blab.sheet = []
 sh = (id, data) ->
     new Sheet {id:id, data:data, compute: true}
@@ -179,7 +178,9 @@

 $blab.sheet['q'].spec.rowHeaders = ['one','two']
 $blab.sheet['q'].spec.colHeaders = ['i','ii','iii','iv','v','vi']
-
+###
+
+###
 # slider

 slid = (id) -> new Slider
@@ -257,6 +258,8 @@
 $blab.markdown =
     md1: markdn()

+###
+
 # user code

@@ -310,8 +313,18 @@
         url: "https://api.github.com/gists/#{gistid}"
         type: 'GET'
         dataType: 'jsonp').success((gistdata) ->
-        content = gistdata.data.files[filename].content
-        console.log "content???", content
+        specs = JSON.parse(gistdata.data.files[filename].content)
+        $blab.sheet = []
+        $blab.table = []
+
+        $blab.sheet[spec.id] = new Sheet spec for spec in specs.sheet
+        $blab.table[spec.id] = new Table spec for spec in specs.table
+        #$blab.sheet[spec.id] = new Sheet spec for spec in specs.sheet
+        #$blab.sheet[spec.id] = new Sheet spec for spec in specs.sheet
+
+        console.log "sheet A???", $blab.sheet["A"]
+        console.log "table A???", $blab.table["A"]
+
         compute()
         return
     ).error (e) ->
@@ -319,8 +332,8 @@
         return
     return

-loadgist("f673df3f600fdeb17608", "sheet.json");
-
-
-
-
+loadgist("f673df3f600fdeb17608", "app.json");
+
+
+
+


sort subdomains in dev menu

sort subdomains in dev menu

Former-commit-id: 13f944c5eaccfd3d74ede9e11823924127063d9a [formerly ff1513a485e91ef9363f17c5bc4bc6352f98cc04] [formerly 6c7e9899178c53985c7f90750c86e74ed484f899 [formerly 440d1492838cba66417790ce980658438a609a99]] [formerly c3e27d34d52540ed28286a45d0b18aab1096d3d7 [formerly 4900a8efe28d1a8d34800861604a644b59f3a10f] [formerly 06d3d34212cbc6ba5550b7ffb6afca6f0552267c [formerly 06d3d34212cbc6ba5550b7ffb6afca6f0552267c [formerly 3baec236db18307ffea71e933997b67748893717]]]]
Former-commit-id: eb30fb0e9227be7318c327e5f53b397508b730d3 [formerly 1e3436333fd8e209470566fcf40cbe345077cdbe] [formerly 32b1216b86735af67ba17039c29fb4a63c2bf738 [formerly e58b43dd3f399f6e1c5512c47756072ad93c32aa]]
Former-commit-id: a3ec3fe6eb4a5b74f963350599e68bfde68058b7 [formerly 8717f8c65d4bc036d1ba06ec85c1763e3550cba7]
Former-commit-id: bd6166c514514a669e844000b462252703051466
Former-commit-id: f590886459ad19ed2c7d0adb62e4a932957f09ba

--- @client/development.coffee
+++ @client/development.coffee
@@ -10,15 +10,17 @@
   displayName: 'Development'

   render : ->
-    subdomains = fetch '/subdomains'
+    subdomains = fetch('/subdomains').subs
     apps = fetch '/apps'
     app = fetch '/application'

-    hues = getNiceRandomHues Math.max(subdomains.subs?.length, apps.apps?.length)
+    hues = getNiceRandomHues Math.max(subdomains?.length, apps.apps?.length)
     submenus = if app.app == 'franklin'
                 ['change subdomain', 'change application']
                else if app.app == 'saas_landing_page'
                 ['change application']
+
+    subdomains.sort((a,b) -> a.name > b.name)

     DIV
       style:
@@ -62,7 +64,7 @@
             if @local.hover_second == 'change subdomain'
               UL null,

-                for sub, idx in subdomains.subs
+                for sub, idx in subdomains
                   LI
                     style:
                       display: 'inline-block'


nest factor options

nest factor options


Adds a touch class in addition to mobile

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -20,6 +20,7 @@
 setupSplitTests = require '../split_test/setup.coffee'
 initNightMode = require '../night_mode/index.coffee'
 initLoggedOutCta = require '../logged_out_cta/index.coffee'
+{ isTouch } = require '../util/device.coffee'

 module.exports = ->
   setMobileClass()
@@ -36,9 +37,13 @@
   /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)

 setMobileClass = ->
+  $body = $('body')
   if isMobile()
-    $('body').addClass 'is-mobile'
-    attachFastClick(document.body)
+    $body.addClass 'is-mobile'
+    attachFastClick document.body
+
+  if isTouch()
+    $body.addClass 'Body--touch'

 setupPusherAndCurrentUser = ->
   mediator.shared = {}


:new: Add a spec about automatically removing JSON attributes

--- spec/custom-element-spec.coffee
+++ spec/custom-element-spec.coffee
@@ -64,6 +64,16 @@
     element.wow = {}
     expect(element.hasAttribute('wow')).toBe(false)

+  it 'automatically removes JSON attributes after using', ->
+    element = new (registerCustomElement(getCustomElement({
+      name: 'x-spec-automatically-remove-json-attributes'
+      config: {wow: {type: 'JSON'}}
+    })))
+    element.setAttribute('wow', '[]')
+    expect(element.hasAttribute('wow')).toBe(false)
+    expect(element.wow instanceof Array).toBe(true)
+    expect(element.wow.length).toBe(0)
+
   it 'automatically uses the existing html attributes in config', ->
     name = 'x-spec-automatically-resume-config-attributes'
     registerCustomElement(getCustomElement({


Only expect prefix when there are 5 or more segments

--- src/extensions/outline-view/src/tag-generator.coffee
+++ src/extensions/outline-view/src/tag-generator.coffee
@@ -24,10 +24,14 @@

     label = sections[0]
     line = parseInt(sections[2]) - 1
-    if prefix = @parsePrefix(sections[4])
-      label = "#{prefix}::#{label}"
-    if signature = @parsePrefix(sections[5])
-      label = "#{label}#{signature}"
+    if sections.length > 5
+      if prefix = @parsePrefix(sections[4])
+        label = "#{prefix}::#{label}"
+      if suffix = @parsePrefix(sections[5])
+        label = "#{label}#{suffix}"
+    else
+      if suffix = @parsePrefix(sections[4])
+        label = "#{label}#{suffix}"

     tag =
       position: new Point(line, 0)


Add better camera view

Add better camera view


retry with enableHighAccuracy=false when getCurrentPosition timeouted

--- views/js/map.coffee
+++ views/js/map.coffee
@@ -7,7 +7,9 @@
   marker_current_location = null
   map_bookmark_model = null
   CURRENT_LOCATION_LABEL = "現在地"
-  pending_current_location = false
+  current_location_pending = false
+  current_location_retry = 0
+

   remove_current_location_marker = ->
     if not _.isNull(marker_current_location)
@@ -86,8 +88,9 @@
       if editable
         elem.find(".marker-edit").removeClass("hide")
         elem.find(".marker-delete").removeClass("hide")
-    switch_pending_current_location_state: (state)->
-      pending_current_location = state
+    switch_current_location_pending_state: (state)->
+      current_location_pending = state
+      current_location_retry = 0
       elem = $("[data-id='CURRENT_LOCATION']").find(".marker-title")
       if state
         elem.html("取得中..")
@@ -99,27 +102,37 @@
       id = $(ev.target).closest("[data-id]").data("id")
       that = this
       if id == "CURRENT_LOCATION"
-        return if pending_current_location
-        @switch_pending_current_location_state(true)
+        return if current_location_pending
+        @switch_current_location_pending_state(true)
         if navigator?.geolocation?.getCurrentPosition?
           onsuccess = (position)->
             c = position.coords
             geo = {lat:c.latitude, lng:c.longitude}
             marker_current_location = show_marker(false, false, geo, {title:CURRENT_LOCATION_LABEL,description:""})
-            mymap.setView(geo)
-            that.switch_pending_current_location_state(false)
+            zoom = Math.max(mymap.getZoom(),15)
+            mymap.setView(geo, zoom)
+            that.switch_current_location_pending_state(false)
           onerror = (err)->
-            _.cb_alert("エラー(#{err.code}): #{err.message}")
-            that.switch_pending_current_location_state(false)
+            if err.code == 3 and current_location_retry == 0
+              # err.code == 3 は TIMEOUT なので enableHighAccuracy: false で再試行してみる
+              current_location_retry = 1
+              console.log "hoge"
+              navigator.geolocation.getCurrentPosition(onsuccess,onerror,{
+                enableHighAccuracy: false
+                timeout: 10000
+                maximumAge: 0
+              })
+            else
+              _.cb_alert("エラー(#{err.code}): #{err.message}")
+              that.switch_current_location_pending_state(false)
           navigator.geolocation.getCurrentPosition(onsuccess,onerror,{
             enableHighAccuracy: true
-            timeout: 15000
+            timeout: 20000
             maximumAge: 0
-
           })
         else
           _.cb_alert("お使いのブラウザでは位置情報を取得できません")
-          that.switch_pending_current_location_state(false)
+          that.switch_current_location_pending_state(false)
       else
         mymap.setView(map_markers[id].marker._latlng)
         map_markers[id].marker.openPopup()


style form for agencies

style form for agencies

--- app/assets/javascripts/components/questions/form.cjsx
+++ app/assets/javascripts/components/questions/form.cjsx
@@ -4,17 +4,25 @@
 class Form extends React.Component
   render: ->
     <form>
-      Agency Name:
-      <input type="text" name="agency_name" id="agency_name"/>
+      <fieldset className="form__group">
+        Agency Name:
+        <input className="form__field" type="text" name="agency_name" id="agency_name"/>
+      </fieldset>

-      Contact Name:
-      <input type="text" name="contact_name" id="contact_name"/>
+      <fieldset className="form__group">
+        Contact Name:
+        <input className="form__field" type="text" name="contact_name" id="contact_name"/>
+      </fieldset>

-      Telephone:
-      <input type="text" name="telephone" id="telephone"/>
+      <fieldset className="form__group">
+        Telephone:
+        <input className="form__field" type="text" name="telephone" id="telephone"/>
+      </fieldset>

-      Email:
-      <input type="text" name="email" id="email"/>

+      <fieldset className="form__group">
+        Email:
+        <input className="form__field" type="text" name="email" id="email"/>

+      </fieldset>
     </form>

 module.exports = Form


allow adding trainings to newly created block

--- app/assets/javascripts/components/timeline/block.cjsx
+++ app/assets/javascripts/components/timeline/block.cjsx
@@ -63,7 +63,7 @@
       spacer = <span>  —  </span>

     modules = undefined
-    if @props.block.training_modules || (@props.block.kind is 1 && @props.editable)
+    if @props.block.training_modules || (parseInt(@props.block.kind) is 1 && @props.editable)
       modules = (
         <TrainingModules
           onChange={@passedUpdateBlock}
@@ -103,7 +103,7 @@
           value={@props.block.kind}
           value_key={'kind'}
           editable={@props.editable}
-          options={['In Class', 'Assignment', 'Milestone', 'Custom', 'Training']}
+          options={['In Class', 'Assignment', 'Milestone', 'Custom']}
           show={@props.block.kind < 3 || @props.editable}
           label='Block type'
           popover_text={I18n.t('timeline.block_type')}


add maps click event (scrolls to first result)

--- app/assets/javascripts/gmaps_search_results.js.coffee
+++ app/assets/javascripts/gmaps_search_results.js.coffee
@@ -48,6 +48,12 @@
     for offerID in markerData.offer_ids
       $("#result-offer-#{offerID}").removeClass 'JS-highlighted'

+  google.maps.event.addListener marker, 'click', (event) ->
+    offerID = markerData.offer_ids[0] # specification needed
+    $('html, body').animate
+      scrollTop: $("#result-offer-#{offerID}").offset().top - 120
+    , 'fast'
+
 bindMarkerToResults = (marker, markerData) ->
   for offerID in markerData.offer_ids
     $("#result-offer-#{offerID}").data('marker', marker)