Just normal Markdown links.

Just normal Markdown links.

--- app/assets/javascripts/autocompleter.js.coffee
+++ app/assets/javascripts/autocompleter.js.coffee
@@ -18,7 +18,7 @@
       callback result
     search_call.fail -> callback([],true)
   replace: (entry)->
-    "cite[#{entry.title.substring(0,10).replace(" ","_")}](#{entry.doi}) "
+    "[#{entry.title.substring(0,10).replace(" ","_")}](#{entry.doi}) "
   template: (entry)->
     "#{entry.title}"


refs #95: housing_forms script now only operates on the forms page by checking the pathname prior to initialization.

--- app/assets/javascripts/housing_forms.js.coffee
+++ app/assets/javascripts/housing_forms.js.coffee
@@ -1,39 +1,39 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
+if window.location.pathname.match(/\/form$/)
+	$ ->

-$ ->
+		changeSection = ->
+			hash = window.location.hash
+			sectionId = hash.substring 1
+			$('.section').each ()->
+				section = $ @
+				if section.attr('id') is sectionId
+					section.toggleClass 'hide', false
+				else
+					section.toggleClass 'hide', true
+			$('.wizard-nav a').each ()->
+				link = $ @
+				parent = link.parent()
+				if link.attr('href') is hash
+					parent.toggleClass 'active', true
+				else
+					parent.toggleClass 'active', false
+			window.scrollTo 0, 0

-  changeSection = ->
-    hash = window.location.hash
-    sectionId = hash.substring 1
-    $('.section').each ()->
-      section = $ @
-      if section.attr('id') is sectionId
-        section.toggleClass 'hide', false
-      else
-        section.toggleClass 'hide', true
-    $('.wizard-nav a').each ()->
-      link = $ @
-      parent = link.parent()
-      if link.attr('href') is hash
-        parent.toggleClass 'active', true
-      else
-        parent.toggleClass 'active', false
-    window.scrollTo 0, 0
+		$(window).on 'hashchange', changeSection
+
+		showDownloadWarning = ->
+			$('#download-warning').modal()

-  $(window).on 'hashchange', changeSection
-
-  showDownloadWarning = ->
-    $('#download-warning').modal()
-
-  initialize = ->
-    $('.download-forms').click(showDownloadWarning)
-    firstSectionHash = $('.wizard-nav a').eq(0).attr 'href'
-    if window.location.hash.length <= 1
-      window.location.hash = firstSectionHash
-    else
-      changeSection()
-
-  $(document).ajaxComplete initialize
-  initialize()
+		initialize = ->
+			$('.download-forms').click(showDownloadWarning)
+			firstSectionHash = $('.wizard-nav a').eq(0).attr 'href'
+			if window.location.hash.length <= 1
+				window.location.hash = firstSectionHash
+			else
+				changeSection()
+
+		$(document).ajaxComplete initialize
+		initialize()


Updated hotkeys for search

Updated hotkeys for search

--- app/assets/javascripts/sugar/hotkeys.js.coffee
+++ app/assets/javascripts/sugar/hotkeys.js.coffee
@@ -199,6 +199,9 @@
       $(document).bind "keydown", "j", (event) ->
         Sugar.Hotkeys.gotoNextTarget()  unless event.metaKey

+      $(document).bind "keydown", "/", (event) ->
+        #('#q').trigger 'focus'  unless event.metaKey
+
     # Post functions
     postsFunctions: ->
       # Load new posts
@@ -242,6 +245,9 @@

       $(document).bind "keydown", "j", (event) ->
         Sugar.Hotkeys.gotoNextTarget()  unless event.metaKey
+
+      $(document).bind "keydown", "/", (event) ->
+        #('#q').trigger 'focus'  unless event.metaKey

     # Discussion functions
     discussionsFunctions: ->


Safe set global Ultimate object

Safe set global Ultimate object

--- app/assets/javascripts/ultimate/base.js.coffee
+++ app/assets/javascripts/ultimate/base.js.coffee
@@ -1,6 +1,6 @@
 #= require ./helpers

-@Ultimate =
+@Ultimate ||=

   debugMode: false


use plural

use plural

--- app/assets/javascripts/utilities/logger.coffee
+++ app/assets/javascripts/utilities/logger.coffee
@@ -1,7 +1,7 @@
 class App.Logger
   @add_error: (error_object) ->
-    @error ||= []
-    @error.push(error_object)
+    @errors ||= []
+    @errors.push(error_object)

 jQueryInit = $.fn.init
 $.fn.init = (selector, context) ->


add dragula in project values

add dragula in project values

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -492,16 +492,16 @@
         # Drag & Drop
         ##################################
         sortableEl = $el.find(".js-sortable")
-
-        sortableEl.sortable({
-            handle: ".js-view-custom-field",
-            dropOnEmpty: true
-            revert: 400
-            axis: "y"
+        drake = dragula([sortableEl[0]], {
+            direction: 'vertical',
+            copySortSource: false,
+            copy: false,
+            mirrorContainer: sortableEl[0],
+            moves: (item) -> return $(item).is('div[tg-bind-scope]')
         })

-        sortableEl.on "sortstop", (event, ui) ->
-            itemEl = ui.item
+        drake.on 'dragend', (item) ->
+            itemEl = $(item)
             itemAttr = itemEl.scope().attr
             itemIndex = itemEl.index()
             $ctrl.moveCustomAttributes(itemAttr, itemIndex)


fix minical highlights when deactivating calendars

fixes CNVS-27746

test plan:
 - make a calendar event
 - hide the calendar that the event's on
 - ensure the highlight on the minical on that day goes away

Change-Id: I8aa3d6a4dc622be75732aeba7289e7675da30686
Reviewed-on: https://gerrit.instructure.com/73721
Reviewed-by: Alex Boyd <6bb5a335b0b6c251e818c0de653d342158f949f9@instructure.com>
Tested-by: Jenkins
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
QA-Review: Heath Hales <8ca81b68c7e2592ac614bd07f22fcc735ca55f93@instructure.com>
Product-Review: Alex Boyd <6bb5a335b0b6c251e818c0de653d342158f949f9@instructure.com>

--- app/coffeescripts/calendar/MiniCalendar.coffee
+++ app/coffeescripts/calendar/MiniCalendar.coffee
@@ -39,7 +39,7 @@
       # getting called every time we switch views, *before* the events are rendered.
       # That makes this a great place to clear out the previous classes.

-      @calendar.find(".fc-content td")
+      @calendar.find(".fc-widget-content td")
         .removeClass("event slot-available")
         .removeAttr('title')
       @mainCalendar.getEvents start, end, timezone, cb


Also allow commenting on <li>s

Also allow commenting on <li>s

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -154,7 +154,7 @@
     for paragraphButton in @_paragraphButtons
       paragraphButton.destroy()

-    @_addParagraphButtonsBatch $('p, h2, h3, h4, h5, h6')
+    @_addParagraphButtonsBatch $('p, h2, h3, h4, h5, h6, li')

 FactlinkJailRoot.loaded_promise.then ->
   paragraphButtons = new FactlinkJailRoot.ParagraphButtons


Display up to a 1k categories.

Display up to a 1k categories.

--- app/assets/javascripts/application/seeds_store.js.coffee
+++ app/assets/javascripts/application/seeds_store.js.coffee
@@ -59,7 +59,8 @@
   updatePods: (pods) ->
     @db.put('pod', pods)
   findCategories: () ->
-    @db.values 'category'
+    iterator = new ydn.db.ValueIterator("category")
+    @db.values iterator, 1000
   findCategory: (name) ->
     keyRange = ydn.db.KeyRange.only(name)
     @db.values 'category', keyRange


Activity: private message keeping scroll positions adjusted to custom scrolls

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -424,13 +424,12 @@

   setScrollTops: ->

-    {body: {scrollHeight}} = document
-    @lastScrollTops.window = window.scrollY or scrollHeight
+    @lastScrollTops.scrollView = @scrollView.wrapper.getScrollTop()

   applyScrollTops: ->

-    window.scrollTo 0, @lastScrollTops.window
+    @scrollView.wrapper.setScrollTop @lastScrollTops.scrollView

   show: ->


Client: Activity: Deleted unnecessary lines to fixed scroll position by shouldScrollToBottom flag

--- client/app/lib/components/scroller/scrollermixin.coffee
+++ client/app/lib/components/scroller/scrollermixin.coffee
@@ -27,8 +27,6 @@

     if @shouldScrollToBottom
       element.scrollTop = element.scrollHeight
-    else
-      element.scrollTop = @scrollTop + (element.scrollHeight - @scrollHeight)

   setScrollPosition: ->


Client: Activity: Renamed scrollToBottom method and added jsdoc for shouldScrollBottom method.

--- client/app/lib/components/scroller/scrollermixin.coffee
+++ client/app/lib/components/scroller/scrollermixin.coffee
@@ -4,14 +4,19 @@

   componentWillUpdate: ->

-    @shouldScrollToBottom = @getScrollToBottomFlag()
+    @shouldScrollToBottom = @shouldScrollBottom()

-  getScrollToBottomFlag: ->
+  shouldScrollBottom: ->

     return  unless @refs?.scrollContainer
+
     { @scrollTop, offsetHeight, @scrollHeight } = React.findDOMNode @refs.scrollContainer
-    @scrollHeight - (@scrollTop + offsetHeight) < 100
+
+    # we can not catch 0px to scroll to bottom. If scroll near about 100px or less
+    # and when new message received we make scroll to bottom so user can see new messages.
+    # If not probably user is reading old messages and we don't make scroll to bottom.
+    return @scrollHeight - (@scrollTop + offsetHeight) < 100

   componentDidUpdate: ->
@@ -32,7 +37,7 @@

     scrollContainer = React.findDOMNode @refs.scrollContainer

-    if @getScrollToBottomFlag()
+    if @shouldScrollBottom()
       scrollContainer.scrollTop = scrollContainer.scrollHeight


video: change video control toggle labels

--- client/app/lib/videocollaboration/views/baseview.coffee
+++ client/app/lib/videocollaboration/views/baseview.coffee
@@ -141,11 +141,11 @@

   title = switch type
     when 'video'
-      activeTooltipText = 'Turn-off Camera'
-      deactiveTooltipText = 'Turn-on Camera'
+      activeTooltipText = 'Turn camera off'
+      deactiveTooltipText = 'Turn camera on'
     when 'audio'
-      activeTooltipText = 'Mute'
-      deactiveTooltipText = 'Unmute'
+      activeTooltipText = 'Mute microphone'
+      deactiveTooltipText = 'Unmute microphone'
     when 'end'
       activeTooltipText = 'End session'
       deactiveTooltipText = 'End session'


use correct score for league leaderboard

--- app/views/ladder/components/Leaderboard.coffee
+++ app/views/ladder/components/Leaderboard.coffee
@@ -140,7 +140,7 @@
           model.get('submittedCodeLanguage'),
           model.rank || index+1,
           (model.get('fullName') || model.get('creatorName') || $.i18n.t("play.anonymous")),
-          model.get('totalScore') * 100 | 0,
+          @correctScore(model),
           @getAgeBracket(model),
           moment(model.get('submitDate')).fromNow().replace('a few ', ''),
           model.get('_id')
@@ -213,3 +213,7 @@

   getAgeBracket: (model) ->
     $.i18n.t("ladder.bracket_#{(model.get('ageBracket') || 'open').replace(/-/g, '_')}")
+
+  correctScore: (model) ->
+    sessionStats = if @league then _.find(model.get('leagues'), {leagueID: @league.id})?.stats else model.attributes
+    return (sessionStats?.totalScore || model.get('totalScore')/2) * 100 | 0


streamlined channel list fetching

streamlined channel list fetching

--- core/app/backbone/controllers/channels_controller.coffee
+++ core/app/backbone/controllers/channels_controller.coffee
@@ -18,8 +18,8 @@

   setChannels: (channels, channel) ->
     channelCollectionView = new ChannelsView(collection: channels)
-    channels.setupReloading();
     channels.setActiveChannel(channel)
+    channels.setupReloading(true)
     FactlinkApp.leftMiddleRegion.show(channelCollectionView)

@@ -32,15 +32,16 @@
       FactlinkApp.leftBottomRegion.close()

     user = channel.user()
-    userView = new UserView(model: user)
-    FactlinkApp.leftTopRegion.show(userView)
+    if user.is_current_user()
+      FactlinkApp.leftTopRegion.close()
+    else
+      userView = new UserView(model: user)
+      FactlinkApp.leftTopRegion.show(userView)

-    if window.Channels.getUsername() == user.get('username')
-      @setChannels(window.Channels, channel)
-    else
-      Channels.setUsername(user.get 'username');
-      window.Channels.fetch
-        success: (channels) => @setChannels(channels, channel)
+
+    if window.Channels.getUsername() isnt user.get('username')
+      window.Channels.setUsername(user.get 'username');
+    @setChannels(window.Channels, channel)


client: stacks: loader color corrected

client: stacks: loader color corrected

--- client/stacks/lib/views/stacks/stacktemplateview.coffee
+++ client/stacks/lib/views/stacks/stacktemplateview.coffee
@@ -38,6 +38,7 @@
       title    : 'Preview'
       cssClass : 'solid compact light-gray template-preview-button'
       loader   : yes
+      loaderOptions: { color: '#858585' }
       tooltip  :
         title  : 'Generates a preview of this template with your own account information.'
       callback : => @emit 'ShowTemplatePreview'


fix test

fix test

--- components/credit_card/test/confirm_registration.coffee
+++ components/credit_card/test/confirm_registration.coffee
@@ -21,8 +21,6 @@
       for method in ['initialize', 'isLoading', 'isLoaded', 'updatePosition']
         sinon.stub ConfirmRegistration.prototype, method
       @view = new ConfirmRegistration
-        paddleNumber: '123'
-        model: new Auction fabricate 'sale', id: 'foo-bar-auction'
         el: $ """

@@ -30,6 +28,7 @@

         """
+      @view.auction = new Auction fabricate 'sale', id: 'foo-bar-auction'
       @view.user = new CurrentUser fabricate 'user'
       done()


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -19,6 +19,7 @@
   player.update()

   player.I.x
+


use environment variables instead of requiring someone else's meshblu.json

--- registry-mangler.coffee
+++ registry-mangler.coffee
@@ -1,13 +1,13 @@
 _ = require 'lodash'
 PROD_INTERVAL_SERVICE_UUID = '765bd3a4-546d-45e6-a62f-1157281083f0'

-localIntervalServiceCreds = require '../nanocyte-interval-redis/meshblu.json'
+LOCAL_INTERVAL_SERVICE_UUID = process.env.NANOCYTE_INTERVAL_UUID

 class RegistryMangler
   mangle: (originalRegistry) =>
     registryString = JSON.stringify originalRegistry, null, 2
     intervalRegex = new RegExp PROD_INTERVAL_SERVICE_UUID, 'g'
-    newRegistryString = registryString.replace intervalRegex, localIntervalServiceCreds.uuid
+    newRegistryString = registryString.replace intervalRegex, LOCAL_INTERVAL_SERVICE_UUID

     newRegistry = JSON.parse newRegistryString
     newRegistry


Fix tests

Fix tests

--- spec/deprecation-cop-view-spec.coffee
+++ spec/deprecation-cop-view-spec.coffee
@@ -48,7 +48,7 @@
       expect(packageItems.length).toBe(3)
       expect(packageItems.eq(0).html()).toMatch /package-1/
       expect(packageItems.eq(1).html()).toMatch /package-2/
-      expect(packageItems.eq(2).html()).toMatch /Atom Core/
+      expect(packageItems.eq(2).html()).toMatch /Other/

       packageDeprecationItems = packageItems.eq(0).find("li.source-file")
       expect(packageDeprecationItems.length).toBe(2)


Stop posting `interactiveStateGlobal` on `modelLoaded` events.

--- src/code/wrapper.coffee
+++ src/code/wrapper.coffee
@@ -78,9 +78,9 @@
       @scheduleDataUpdate()
     "#{@datasetName}-dataReset":=>
       @scheduleDataUpdate()
-    "modelLoaded": () =>
-      l.info("Wrapper: Model loaded called")
-      @runtimePhone.post('interactiveStateGlobal', @globalState)
+    # "modelLoaded": () =>
+    #   l.info("Wrapper: Model loaded called")
+    #   @interactivePhone.post('interactiveStateGlobal', @globalState)
     "dataset": (data)=>
       @globalState[@globalStateKey] = data
       @runtimePhone.post('interactiveStateGlobal', @globalState)


Card examples

Card examples

--- src/coffee/index.coffee
+++ src/coffee/index.coffee
@@ -15,6 +15,10 @@
   card4 = new App.Models.CardModel(cardId: '04031')
   card5 = new App.Models.CardModel(cardId: '04032', faceUp: true)
   card6 = new App.Models.CardModel(cardId: '03047', faceUp: true)
+  card7 = new App.Models.CardModel(cardId: '03048', faceUp: true)
+  card8 = new App.Models.CardModel(cardId: '03049', faceUp: true)
+  card9 = new App.Models.CardModel(cardId: '03049', faceUp: true)
+  card10 = new App.Models.CardModel(cardId: '04033', faceUp: true)

   noCards = new App.Models.CardCollection()
   noCardsPile1 = new App.Models.PileModel(cards: noCards)
@@ -28,8 +32,8 @@
   runnerPiles = new App.Models.PileCollection([noCardsPile1, runnerPile1, runnerPile2, noCardsPile2])
   corpPiles = new App.Models.PileCollection([noCardsPile1, corpPile1, corpPile2, noCardsPile2])

-  runnerHand = new App.Models.CardCollection([card1, card6])
-  corpHand = new App.Models.CardCollection([card4, card5])
+  runnerHand = new App.Models.CardCollection([card9, card6, card7, card8])
+  corpHand = new App.Models.CardCollection([card10, card5])

   runnerModel = new App.Models.PlayerModel(side: 'runner', piles: runnerPiles, hand: runnerHand)
   corpModel = new App.Models.PlayerModel(side: 'corp', piles: corpPiles, hand: corpHand)


Slightly fix documentation.

Slightly fix documentation.

--- src/fs_utils/file_list.coffee
+++ src/fs_utils/file_list.coffee
@@ -5,8 +5,8 @@
 logger = require '../logger'
 sysPath = require 'path'

-# A list of `fs_utils.SourceFile` with some additional methods
-# used to simplify file reading / removing.
+# A list of `fs_utils.SourceFile` or `fs_utils.Asset`
+# with some additional methods used to simplify file reading / removing.
 module.exports = class FileList extends EventEmitter
   # Maximum time between changes of two files that will be considered
   # as a one compilation.


Fix trigger message

Fix trigger message

--- plugins/campfire/index.coffee
+++ plugins/campfire/index.coffee
@@ -6,7 +6,7 @@
     if event.error
       message = "#{event.error.exceptionClass}" + (if event.error.message then ": #{event.error.message}" else "") + " (#{event.error.url})"
     else
-      message =  "#{event.trigger.name} in #{event.project.name} (#{event.project.url})" + ( if event.error then " in #{event.error.context} (#{event.error.url})" else "")
+      message =  "#{event.trigger.message} in #{event.project.name} (#{event.project.url})" + ( if event.error then " in #{event.error.context} (#{event.error.url})" else "")

     # Build the request
     payload =


activity: "comment out" legacy comment view instantiation blocks

--- client/Social/Activity/views/comments/commentview.coffee
+++ client/Social/Activity/views/comments/commentview.coffee
@@ -30,9 +30,9 @@
       delegate      : @
     , data

-    @commentController        = new CommentListViewController view: @commentList
-    @addSubView showMore      = new CommentViewHeader delegate: @commentList, data
-    @addSubView @commentController.getView()
+    # @commentController        = new CommentListViewController view: @commentList
+    # @addSubView showMore      = new CommentViewHeader delegate: @commentList, data
+    # @addSubView @commentController.getView()
     @addSubView @commentForm  = new NewCommentForm delegate : @commentList

     @commentList.on 'ReplyLinkClicked', (username) =>
@@ -58,8 +58,8 @@
     if data.replies
       for reply in data.replies  when reply? and reply.originId? and reply.originType?
         @commentList.addItem reply
-    else
-      @commentController.fetchRelativeComments null, data.meta.createdAt, no, -1
+    # else
+    #   @commentController.fetchRelativeComments null, data.meta.createdAt, no, -1

     @commentList.emit "BackgroundActivityFinished"


activity: fix focus method name for new input view

--- client/Social/Activity/views/comments/inputwidget.coffee
+++ client/Social/Activity/views/comments/inputwidget.coffee
@@ -38,7 +38,7 @@
   unlockSubmit: -> @locked = no

-  setFocus: -> @input.focus()
+  setFocus: -> @input.setFocus()

   submit: (value) ->


Client: Activity: Updated chatlistitem editing functionality for last message editing when pressed up-arrow key

--- client/activity/lib/components/chatlistitem/index.coffee
+++ client/activity/lib/components/chatlistitem/index.coffee
@@ -33,7 +33,6 @@
   @defaultProps =
     hover                         : no
     account                       : null
-    editMode                      : no
     isDeleting                    : no
     isMenuOpen                    : no
     editInputValue                : ''
@@ -50,7 +49,7 @@
     @state =
       hover                         : @props.hover
       account                       : @props.account
-      editMode                      : @props.editMode
+      editMode                      : @props.message.get '__isEditing'
       isDeleting                    : @props.isDeleting
       isMenuOpen                    : @props.isMenuOpen
       editInputValue                : @props.message.get 'body'
@@ -62,6 +61,11 @@
   componentDidMount: ->

     @getAccountInfo()
+
+
+  componentDidUpdate: ->
+
+    @setState editMode: @props.message.get '__isEditing'

   getAccountInfo: ->
@@ -184,7 +188,10 @@

   editPost: ->

-    @setState editMode: yes
+    messageId = @props.message.get '_id'
+
+    ActivityFlux.actions.message.setMessageEditMode messageId
+
     domNode = @refs.EditMessageTextarea.getDOMNode()
     kd.utils.wait 100, ->
       kd.utils.moveCaretToEnd domNode
@@ -230,7 +237,8 @@

   updateMessage: ->

-    @setState editMode: no
+    messageId = @props.message.get '_id'
+    ActivityFlux.actions.message.unsetMessageEditMode messageId

     ActivityFlux.actions.message.editMessage(
       @props.message.get('id')
@@ -241,7 +249,10 @@

   cancelEdit: ->

-    @setState editMode: no, editInputValue: @props.message.get('body')
+    messageId = @props.message.get '_id'
+    ActivityFlux.actions.message.unsetMessageEditMode messageId
+
+    @setState editInputValue: @props.message.get('body')

   onMenuToggle: (isMenuOpen) -> @setState { isMenuOpen }
@@ -267,6 +278,7 @@
   getEditModeClassNames: -> classnames
     'ChatItem-updateMessageForm': yes
     'hidden' : not @state.editMode
+    'visible' : @state.editMode

   getMediaObjectClassNames: -> classnames


Hackathon: scroll improved

Hackathon: scroll improved

--- client/landing/site.hackathon/coffee/home/AppView.coffee
+++ client/landing/site.hackathon/coffee/home/AppView.coffee
@@ -49,9 +49,15 @@
       click   : ->
         $(document.body).animate
           scrollTop : window.innerHeight - 50
-          duration  : 300
+          easing    : 'ease-out'
+          duration  : 177

     @addSubView scroller, '.introduction'
+
+    KD.singletons.windowController.on 'ScrollHappened', ->
+      if window.scrollY > 50
+      then scroller.setClass 'out'
+      else scroller.unsetClass 'out'

   createJoinForm : ->


Fix initial save

Fix initial save

--- src/trix/controllers/editor_element_controller.coffee
+++ src/trix/controllers/editor_element_controller.coffee
@@ -6,7 +6,7 @@
   constructor: (@element, @documentElement, @inputElement) ->
     @contentType = @element.getAttribute("content-type") ? "text/html"

-  save: ->
+  save: =>
     value = Trix.serializeToContentType(@documentElement, @contentType)
     @inputElement.value = value
     @element.setAttribute("value", value)
@@ -22,11 +22,12 @@
     unless @loaded
       @load()
       @loaded = true
-    @save()

   didChangeDocument: (document) ->
     if @loaded
       defer(@saveAndNotify)
+    else
+      defer(@save)

   shouldAcceptFile: (file) ->
     triggerEvent("trix-file-accept", onElement: @element, attributes: {file})


A little bit of cleanup

A little bit of cleanup

--- src/compile.glsl.coffee
+++ src/compile.glsl.coffee
@@ -50,7 +50,6 @@
     "\nint sceneId(in vec3 #{rayOrigin}) {\n  int id = -1;\n#{prelude}  #{code};\n  return id;\n}\n\n"

   sceneMaterial = (materials) ->
-
     binarySearch = (start, end) ->
       diff = end - start
       if diff == 1
@@ -59,27 +58,11 @@
         mid = Math.floor (diff * 0.5)
         "(id < #{mid}? #{binarySearch start, mid} : #{binarySearch mid, end})"

-    #result = "\nvec3 sceneMaterial(in vec3 ro) {\n  int id = sceneId(ro);\n"
-    #if materials.length > 0
-    #  for i in [0..materials.length]
-    #    m = materials[i]
-    #    result += "  vec3 m#{i} = #{m};\n"
-    #result += "  return id >= 0?" m[id] : "vec3(0.5);\n"
-    #result += "}\n\n"
-    #result
     result = "\nvec3 sceneMaterial(in vec3 ro) {\n  int id = sceneId(ro);\n"
     if materials.length > 0
-      #result += "  const vec3 m[#{materials.length}] = vec3[](\n"
-      #result += "    #{materials[0]}"
-      #for m in materials[1..materials.length]
-      #  result += ",\n    #{m}"
-      #result += "\n  );\n"
-      #for i in [0...materials.length]
-      #  result += "  m[#{i}] = #{materials[i]};\n"
       for i in [0...materials.length]
         m = materials[i]
         result += "  vec3 m#{i} = #{m};\n"
-    #result += "  return id >= 0? m[id] : vec3(0.5);\n"
     result += "  return id >= 0? #{binarySearch 0, materials.length} : vec3(0.5);\n"
     result += "}\n\n"
     result


Use hack for valid hash attr

Use hack for valid hash attr

--- components/inquiry_questionnaire/views/basic_info.coffee
+++ components/inquiry_questionnaire/views/basic_info.coffee
@@ -16,16 +16,11 @@
     form.state 'loading'

     @user.set form.data()
-
-    # Temporary hack around API bug surrounding valid hash fields...
-    collectorProfile = @user.related().collectorProfile
-    id = collectorProfile.id
-    collectorProfile.clear()
-    collectorProfile.set _.extend id: id, @user.pick(collectorProfile.validHashFields)
+    @user.related().collectorProfile.setWithValidAttributes @user.attributes

     $.when.apply(null, [
       @user.save()
-      collectorProfile.save()
+      @user.related().collectorProfile.save()
     ])
       .always => @next()


Add top ranks to pages list

Add top ranks to pages list

--- resources/assets/coffee/react/profile-page/extra.coffee
+++ resources/assets/coffee/react/profile-page/extra.coffee
@@ -75,7 +75,7 @@

     withMePage = @props.userPage.html != '' || @props.withEdit

-    pages = ['recent_activities', 'kudosu']
+    pages = ['recent_activities', 'kudosu', 'top_ranks']
     pages.unshift 'me' if withMePage

     tabsContainerClasses = 'profile-extra-tabs__container js-fixed-element'


プレビューのキャラクターの倍率を変数にした

プレビューのキャラクターの倍率を変数にした

--- app/assets/javascripts/views/character_modal_view.js.coffee
+++ app/assets/javascripts/views/character_modal_view.js.coffee
@@ -1,4 +1,4 @@
-# キャラクター選択ダイアログを表現するビュー
+# キャラクター設定ダイアログを表現するビュー
 Smalruby.CharacterModalView = Backbone.View.extend
   model: new Smalruby.Character()

@@ -6,11 +6,12 @@
     'click #character-modal-costume-selecter a': 'onSelectCostume'
     'click #character-modal-ok-button': 'onOk'

+  previewZoomLevel: 0.5
+
   initialize: ->
     @.target = null

     self = @
-    zoom = 2

     $('#character-modal-character').draggable
       containment: '#character-modal-preview'
@@ -18,15 +19,15 @@
       drag: (event, ui) ->
         pos = ui.position
         self.model.set
-          x: pos.left * zoom
-          y: pos.top * zoom
+          x: pos.left / self.previewZoomLevel
+          y: pos.top / self.previewZoomLevel

     $('#character-modal-preview').droppable
       drop: (event, ui) ->
         pos = ui.draggable.position()
         self.model.set
-          x: pos.left * zoom
-          y: pos.top * zoom
+          x: pos.left / self.previewZoomLevel
+          y: pos.top / self.previewZoomLevel

     @.$el.find('input[name="character[name]"]').keypress (e) ->
       e = window.event if !e
@@ -71,12 +72,12 @@
   onChangeX: (model, value, options) ->
     @.$el.find('input[name="character[x]"]').val(value)
     $('#character_x_value').text(value)
-    $('#character-modal-character').css('left', value / 2)
+    $('#character-modal-character').css('left', value * @.previewZoomLevel)

   onChangeY: (model, value, options) ->
     @.$el.find('input[name="character[y]"]').val(value)
     $('#character_y_value').text(value)
-    $('#character-modal-character').css('top', value / 2)
+    $('#character-modal-character').css('top', value * @.previewZoomLevel)

   onChangeAngle: (model, value, options) ->
     @.$el.find('input[name="character[angle]"]').val(value)


Don't require mixins directory in story app js.

--- app/assets/javascripts/voluntary_survey/story/app.js.coffee
+++ app/assets/javascripts/voluntary_survey/story/app.js.coffee
@@ -1,5 +1,4 @@
 #= require_tree ./adapters
-#= require_tree ./mixins
 #= require_tree ./controllers
 #= require_tree ./models
 #= require ./router.js.coffee


close bottom sheet on item selection

close bottom sheet on item selection

--- app/directives/group-page-toolbar/group-page-toolbar.coffee
+++ app/directives/group-page-toolbar/group-page-toolbar.coffee
@@ -18,9 +18,11 @@

       $scope.openInvitePeople = ->
         console.log('invite-people-btn clicked!')
-
+        $mdBottomSheet.cancel()
+
       $scope.openManageFunds = ->
         $location.path("/groups/#{$scope.group.id}/manage_funds")
+        $mdBottomSheet.cancel()

       $scope.openBottomSheet = ->
         $mdBottomSheet.show({


Add support for Twitch client ID (rate limiting)

--- twitch.coffee
+++ twitch.coffee
@@ -23,6 +23,7 @@
 #
 # Config
 #
+TWITCH_API_KEY = process.env.TWITCH_API_KEY
 TWITCH_MAX_RESULTS = 5
 TWITCH_STORAGE_KEY = process.env.HUBOT_TWITCH_KEY or "_twitch"

@@ -123,6 +124,7 @@
                 msg.send "#{i + 1}. #{gameObj.game.name} | Viewers: #{gameObj.viewers} | Channels: #{gameObj.channels} | #{createGameURL(gameObj.game.name)}"

 GetTwitchResult = (msg, api, params = {}, handler) ->
+    params.client_id = TWITCH_API_KEY
     params.limit or= TWITCH_MAX_RESULTS
     msg.http("https://api.twitch.tv/kraken#{api}")
         .query(params)


Allow params in run-now of scheduled

Allow params in run-now of scheduled

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -274,11 +274,8 @@
                 requestModel.run(confirmedOrPromptData)
                 utils.flashRow $row

-        if requestType is 'on-demand'
-            dialogType = vex.dialog.prompt
-            dialogOptions.message += '
Additional command line input (optional):
'
-        else
-            dialogType = vex.dialog.confirm
+        dialogType = vex.dialog.prompt
+        dialogOptions.message += '
Additional command line input (optional):
'

         dialogType dialogOptions


'All Requests' are ASC by default

'All Requests' are ASC by default

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -151,9 +151,6 @@

         else
             context.requests = _.pluck(@collection.models, 'attributes')
-
-        if @lastRequestsFilter is 'all'
-            context.requests.reverse()

         for request in context.requests
             if app.collections.requestsStarred.get(request.name)?


Add names to panels.

Add names to panels.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -30,16 +30,19 @@
           views         : [
             {
               type      : "custom"
+              name      : "filesPane"
               paneClass : IDEFilesTabView
             },
             {
               type      : "custom"
+              name      : "editorPane"
               paneClass : IDETabView
             }
           ]
         },
         {
           type          : "custom"
+          name          : "socialsPane"
           paneClass     : IDESocialsTabView
         }
       ]


Editor: ListItem class for listing current vms.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -93,3 +93,18 @@
         @controller.unmountVm info.hostnameAlias

+class VMListItem extends KDListItemView
+
+  constructor:(options={}, data)->
+    super options, data
+    {hostnameAlias} = @getData()
+    KD.singletons.vmController.info hostnameAlias, (err, name, info)=>
+      return KD.showError err if err
+      @addSubView vmLabel  = new KDLabelView title: hostnameAlias
+      @addSubView vmSwitch = new KodingSwitch
+        cssClass     : 'dark'
+        defaultValue : if info.state is "RUNNING" then true else false
+        callback     : (state)=>
+          @getDelegate().emit "VmStateChanged", {state, hostnameAlias}
+
+  viewAppended:JView::viewAppended


name variable is actually called attr

name variable is actually called attr

--- client/lanes/models/Base.coffee
+++ client/lanes/models/Base.coffee
@@ -245,8 +245,8 @@
         else if @unmaskedInvalidFields isnt 'all'
             @unmaskedInvalidFields ||= []
             if _.include(@requiredAttributes, attr) and !_.include(@unmaskedInvalidFields, attr)
-                @unmaskedInvalidFields.push(name)
-                @trigger("invalid-field:#{name}", this)
+                @unmaskedInvalidFields.push(attr)
+                @trigger("invalid-field:#{attr}", this)

     _calculateInvalidAttributes: ->
         invalid = []


reload

reload

--- evilbot.coffee
+++ evilbot.coffee
@@ -17,6 +17,7 @@
 http   = require 'http'
 qs     = require 'querystring'
 env    = process.env
+exec   = require('child_process').exec

 #
@@ -136,6 +137,11 @@
 hear /about/, (message) ->
   message.say "I am learning to love."

+hear /reload/, (message) ->
+  message.say "Reloading…"
+  exec "git fetch origin && git reset --hard origin/master", ->
+    process.exit(1)
+
 hear /help/, (message) ->
   message.say "I listen for the following…", ->
     for phrase, functionality of descriptions


v0.0.3

--- src/jquery.slideMatrix.coffee
+++ src/jquery.slideMatrix.coffee
@@ -1,5 +1,5 @@
 ###
-jquery.slideMatrix v0.0.2
+jquery.slideMatrix v0.0.3
 (c) 2012 Ryan Lewis <ryanscottlewis@gmail.com> MIT License
 ###


Clean gruntfile.coffee

Clean gruntfile.coffee

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -30,12 +30,9 @@
     watch:
       options:
         livereload: true
-      stylus:
-        files: ['src/*.styl']
-        tasks: ['stylus']
-      jade:
-        files: ['test/*.jade']
-        tasks: ['jade']
+      default:
+        files: ['src/*.styl', 'test/*.jade']
+        tasks: ['build']

   grunt.registerTask 'build', ['stylus', 'jade']
   grunt.registerTask 'serve', ['connect', 'watch']


[grunt] added path to locales

[grunt] added path to locales

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -35,6 +35,7 @@
         icons: '<%= path.source.root %>/icons'
         images: '<%= path.source.root %>/images'
         styles: '<%= path.source.root %>/styles'
+        locales: '<%= path.source.root %>/locales'
         layouts: '<%= path.source.root %>/layouts'
         scripts: '<%= path.source.root %>/scripts'
         sprites: '<%= path.source.root %>/sprites'


Added dist file to bump commit task

Added dist file to bump commit task

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -28,7 +28,7 @@
     bump:
       options:
         files: ['package.json', 'bower.json']
-        commitFiles: ['package.json', 'bower.json']
+        commitFiles: ['package.json', 'bower.json', 'dist/bible.js']
         pushTo: 'origin'


Refactor gruntfile

Refactor gruntfile

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -45,8 +45,8 @@
       extensions:
         app     : []
         appnima : ['extensions/appnima/**/*.coffee']
+        carousel: ['extensions/carousel/**/*.coffee']
         gmaps   : ['extensions/gmaps/**/*.coffee']
-        carousel: ['extensions/carousel/**/*.coffee']

       # Stylesheets
       stylus:
@@ -63,23 +63,34 @@
           'extensions/app/style/theme/organism.*.styl'
           'extensions/app/style/theme/app.styl'
         ]
-        # Extensions
         icons: [
           'extensions/icons/*.styl']
-        appnima: [
+        app_appnima: [
           'extensions/appnima/stylesheets/*.styl']
-        gmaps: [
+        app_carousel: [
+          'extensions/carousel/stylesheets/*.styl']
+        app_gmaps: [
           'extensions/gmaps/stylesheets/*.styl']

+    concat:
+      core        : files: '<%=meta.build%>/<%=pkg.name%>.debug.coffee'           : '<%= source.core %>'
+      # Extension
+      app         : files: '<%=meta.build%>/<%=pkg.name%>.app.coffee'             : '<%= source.app %>'
+      app_appnima : files: '<%=meta.build%>/<%=pkg.name%>.app.appnima.coffee'     : '<%= source.extensions.appnima %>'
+      app_carousel: files: '<%=meta.build%>/<%=pkg.name%>.app.carousel.coffee'    : '<%= source.extensions.carousel %>'
+      app_gmaps   : files: '<%=meta.build%>/<%=pkg.name%>.app.gmaps.coffee'       : '<%= source.extensions.gmaps %>'
+      # Example
+      example_app : files: '<%=meta.build%>/<%=pkg.name%>.example.app.coffee'     : '<%= source.example.app %>'
+

     coffee:
-      options: join: true
       core        : files: '<%=meta.build%>/<%=pkg.name%>.debug.js'               : '<%=meta.build%>/<%=pkg.name%>.debug.coffee'
       spec        : files: '<%=meta.build%>/<%=pkg.name%>.spec.js'                : '<%= source.spec %>'
       # Extension
       app         : files: '<%=meta.build%>/<%=pkg.name%>.app.js'                 : '<%=meta.build%>/<%=pkg.name%>.app.coffee'
-      appnima     : files: '<%=meta.build%>/<%=pkg.name%>.app.appnima.js'         : '<%=meta.build%>/<%=pkg.name%>.app.appnima.coffee'
-      gmaps       : files: '<%=meta.build%>/<%=pkg.name%>.app.gmaps.js'           : '<%=meta.build%>/<%=pkg.name%>.app.gmaps.coffee'
+      app_appnima : files: '<%=meta.build%>/<%=pkg.name%>.app.appnima.js'           : '<%=meta.build%>/<%=pkg.name%>.app.appnima.coffee'
+      app_carousel: files: '<%=meta.build%>/<%=pkg.name%>.app.carousel.js'           : '<%=meta.build%>/<%=pkg.name%>.app.carousel.coffee'
+      app_gmaps   : files: '<%=meta.build%>/<%=pkg.name%>.app.gmaps.js'           : '<%=meta.build%>/<%=pkg.name%>.app.gmaps.coffee'
       # Example
       example_app : files: '<%=meta.build%>/<%=pkg.name%>.example.app.js'         : '<%=meta.build%>/<%=pkg.name%>.example.app.coffee'

@@ -93,13 +104,15 @@
       app:
         options: mangle: false
         files: '<%=meta.bower%>/<%=pkg.name%>.app.js'             : '<%=meta.build%>/<%=pkg.name%>.app.js'
-      appnima:
+      app_appnima:
         options: mangle: false
         files: '<%=meta.extensions%>/appnima/<%=pkg.name%>.app.appnima.js'       : '<%=meta.build%>/<%=pkg.name%>.app.appnima.js'
-      gmaps:
+      app_carousel:
+        options: mangle: false
+        files: '<%=meta.extensions%>/carousel/<%=pkg.name%>.app.carousel.js'       : '<%=meta.build%>/<%=pkg.name%>.app.carousel.js'
+      app_gmaps:
         options: mangle: false
         files: '<%=meta.extensions%>/gmaps/<%=pkg.name%>.app.gmaps.js'       : '<%=meta.build%>/<%=pkg.name%>.app.gmaps.js'
-

     jasmine:
       pivotal:
@@ -117,16 +130,19 @@
       theme:
         options: compress: false, import: [ '__init']
         files: '<%=meta.bower%>/<%=pkg.name%>.app.theme.css': '<%=source.stylus.theme%>'
-      # Extensions
       icons:
         options: compress: true
         files: '<%=meta.extensions%>/icons/<%=pkg.name%>.icons.css': '<%=source.stylus.icons%>'
-      appnima:
-        options: compress: true
-        files: '<%=meta.extensions%>/appnima/<%=pkg.name%>.app.appnima.css': '<%=source.stylus.appnima%>'
-      gmaps:
-        options: compress: true
-        files: '<%=meta.extensions%>/gmaps/<%=pkg.name%>.app.gmaps.css': '<%=source.stylus.gmaps%>'
+      app_appnima:
+        options: compress: true
+        files: '<%=meta.extensions%>/appnima/<%=pkg.name%>.app.appnima.css': '<%=source.stylus.app_appnima%>'
+      app_carousel:
+        options: compress: true
+        files: '<%=meta.extensions%>/carousel/<%=pkg.name%>.app.carousel.css': '<%=source.stylus.app_carousel%>'
+      app_gmaps:
+        options: compress: true
+        files: '<%=meta.extensions%>/gmaps/<%=pkg.name%>.app.gmaps.css': '<%=source.stylus.app_gmaps%>'
+

     notify:
       core:
@@ -144,19 +160,16 @@
     watch:
       core:
         files: ['<%= source.core %>']
-        tasks: ['coffee:core', 'uglify:core']#, 'jasmine', 'notify:core']
+        tasks: ['concat:core', 'coffee:core', 'uglify:core']#, 'jasmine', 'notify:core']
       spec:
         files: ['<%= source.spec %>']
         tasks: ['coffee:spec', 'jasmine', 'notify:spec']
       app:
         files: ['<%= source.app %>']
-        tasks: ['coffee:app', 'uglify:app', 'notify:app']
-      app_appnima:
-        files: ['<%= source.extensions.appnima %>']
-        tasks: ['coffee:appnima', 'uglify:appnima']
+        tasks: ['concat:app', 'coffee:app', 'uglify:app', 'notify:app']
       app_gmaps:
         files: ['<%= source.extensions.gmaps %>']
-        tasks: ['coffee:gmaps', 'uglify:gmaps']
+        tasks: ['concat:app_gmaps', 'coffee:app_gmaps', 'uglify:app_gmaps']
       stylus_app:
         files: ['<%= source.stylus.app %>']
         tasks: ['stylus:app', 'notify:stylus_app']
@@ -166,22 +179,26 @@
       stylus_icons:
         files: ['<%= source.stylus.icons %>']
         tasks: ['stylus:icons']
-      stylus_appnima:
-        files: ['<%= source.stylus.appnima %>']
-        tasks: ['stylus:appnima']
-      stylus_gmaps:
-        files: ['<%= source.stylus.gmaps %>']
-        tasks: ['stylus:gmaps']
+      stylus_app_appnima:
+        files: ['<%= source.stylus.app_appnima %>']
+        tasks: ['stylus:app_appnima']
+      stylus_app_carousel:
+        files: ['<%= source.stylus.app_carousel %>']
+        tasks: ['stylus:app_carousel']
+      stylus_app_gmaps:
+        files: ['<%= source.stylus.app_gmaps %>']
+        tasks: ['stylus:app_gmaps']
       example_app:
         files: ['<%= source.example.app %>']
-        tasks: ['coffee:example_app']
+        tasks: ['concat:example_app', 'coffee:example_app']

   grunt.loadNpmTasks 'grunt-contrib-coffee'
+  grunt.loadNpmTasks 'grunt-contrib-concat'
   grunt.loadNpmTasks 'grunt-contrib-uglify'
   grunt.loadNpmTasks 'grunt-contrib-jasmine'
   grunt.loadNpmTasks 'grunt-contrib-stylus'
   grunt.loadNpmTasks 'grunt-notify'
   grunt.loadNpmTasks 'grunt-contrib-watch'

-  grunt.registerTask 'default', ['coffee', 'uglify', 'stylus', 'jasmine']
+  grunt.registerTask 'default', ['concat', 'coffee', 'uglify', 'stylus', 'jasmine']


Allow arrays as param lists in

Allow arrays as param lists in

--- src/loophole.coffee
+++ src/loophole.coffee
@@ -19,7 +19,9 @@
 exports.Function = (paramLists..., body) ->
   params = []
   for paramList in paramLists
-    params.push(paramList.split(/\s*,\s*/)...)
+    if typeof paramList is 'string'
+      paramList = paramList.split(/\s*,\s*/)
+    params.push(paramList...)

   vm.runInThisContext """
     (function(#{params.join(', ')}) {


try this

try this

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -105,6 +105,8 @@
   app.use session
     secret: SESSION_SECRET
     domain: COOKIE_DOMAIN
+    maxage: (1000 * 60 * 60 * 24 * 365) # 1 Year
+    secureProxy: true
     key: 'force.sess'
   app.use artsyPassport _.extend config,
     CurrentUser: CurrentUser


Makes development errors easier to read

Makes development errors easier to read

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -154,11 +154,13 @@
   if NODE_ENV is 'development'
     app.use (err, req, res, next) =>
       res.status(err.status or 500)
-      res.json({
-        message: err.message,
-        status: err.status,
-        stack: err.stack,
-      })
+      res.send("""
+
#{err.status or 500}

+
Message

+
#{err.message}

+
Stacktrace

+
#{err.stack}

+      """)
   else
     # TODO: Kill this/replace with something that's not a Node module
     artsyError.handlers app,


Adds the attributes needed to detect eligibility

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -111,29 +111,32 @@
     CurrentUser: CurrentUser
     SECURE_ARENA_URL: API_URL
     userKeys: [
-      'id',
-      'first_name',
-      'last_name',
-      'email',
-      'slug',
-      'following_ids',
-      'notification_count',
-      'username',
-      'authentication_token',
-      'manifest',
-      'announcements',
-      'shortcuts_id',
-      'avatar_image',
-      'registered',
-      'receive_email',
-      'receive_newsletter',
-      'post_address',
-      'show_tour',
-      'is_premium',
-      'channel_count',
-      'exclude_from_indexes',
-      'following_count',
+      'id'
+      'first_name'
+      'last_name'
+      'email'
+      'slug'
+      'following_ids'
+      'notification_count'
+      'username'
+      'authentication_token'
+      'manifest'
+      'announcements'
+      'shortcuts_id'
+      'avatar_image'
+      'registered'
+      'receive_email'
+      'receive_newsletter'
+      'post_address'
+      'show_tour'
+      'is_premium'
+      'channel_count'
+      'exclude_from_indexes'
+      'following_count'
       'home_path'
+      'created_at'
+      'private_connections_count'
+      'private_connections_limit'
     ]

   app.use artsyError.helpers


bump version to 1.0.0

bump version to 1.0.0


Removing references to builder from gruntfile

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -10,16 +10,12 @@
           sourceMap: true
           sourceMapDir: 'dist/'
         files:
-          'dist/moltin.builder.js': ['src/builder.coffee', 'src/builder/loader.coffee', 'src/builder/*.coffee']
           'dist/moltin.js': ['src/moltin.coffee', 'src/features/storage.coffee', 'src/features/*.coffee']
     concat:
       options:
         separator: ';'
         banner: '/*! <%= pkg.name %> minified - v<%= pkg.version %> - ' +
           '<%= grunt.template.today("yyyy-mm-dd") %> */' + "\n"
-      builder:
-        src: ['dist/moltin.builder.js', 'src/dot.js']
-        dest: 'dist/moltin.builder.js'
       css:
         src: ['src/css/*.css']
         dest: 'dist/moltin.css'
@@ -38,7 +34,6 @@
           banner: '/*! <%= pkg.name %> minified - v<%= pkg.version %> - ' +
           '<%= grunt.template.today("yyyy-mm-dd") %> */'
         files:
-          'dist/moltin.builder.min.js': ['dist/moltin.js', 'dist/moltin.builder.js']
           'dist/moltin.min.js': 'dist/moltin.js'
     cssmin:
       compress:
@@ -56,7 +51,7 @@
         src: ['*.min.js', '*.min.css', '*.min.map']
         dest: 'dist/gzip/'
     watch:
-      files: ['src/*.coffee', 'src/features/*.coffee', 'src/builder/*.coffee', 'src/css/*.css']
+      files: ['src/*.coffee', 'src/features/*.coffee', 'src/css/*.css']
       tasks: ['coffee', 'concat', 'karma', 'uglify', 'cssmin', 'compress']

   # Do we have credentials?
@@ -70,7 +65,6 @@
       aws:
         files: [
           {flatten: true, src: 'dist/gzip/moltin.min.js', dest: 'dist/gzip/v1', filter: 'isFile'}
-          {flatten: true, src: 'dist/gzip/moltin.builder.min.js', dest: 'dist/gzip/builder', filter: 'isFile'}
         ]

     # Upload files to s3.
@@ -86,15 +80,10 @@
           ContentEncoding: 'gzip'
         mime:
           'dist/gzip/v1': 'application/javascript; charset=utf-8',
-          'dist/gzip/builder': 'application/javascript; charset=utf-8'
       production:
         files: [
           {expand: true, cwd: 'dist/gzip/', src: ['**'], dest: '/'}
-          {src: 'builder', dest: 'v1/', action: 'copy'}
-          {src: 'moltin.builder.min.js', dest: 'v1/', action: 'copy'}
-          {src: 'moltin.builder.min.map', dest: 'v1/', action: 'copy'}
           {src: 'moltin.min.css', dest: 'v1/', action: 'copy'}
-          {dest: '/', exclude: 'builder', flipExclude: true, action: 'delete'}
         ]

   # Initialize grunt.


- [grunt] small style improvement in grunfile

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -6,7 +6,7 @@

   # Define the configuration for all the tasks
   grunt.initConfig
-    pkg: grunt.file.readJSON('package.json')
+    pkg: grunt.file.readJSON 'package.json'

     # Specify your source and build directory structure
     path:


fix export css issue

fix export css issue

--- lib/style.coffee
+++ lib/style.coffee
@@ -45,6 +45,7 @@
           .join('\n')
           .replace(/atom-text-editor/g, 'pre.editor-colors')
           .replace(/:host/g, '.host') # Remove shadow-dom :host selector causing problem on FF
+          .replace(/syntax\-\-/g, '')

 module.exports = {
   getTextEditorStyles


Fix for null camera angles.

Fix for null camera angles.

--- packages/landsofillusions-assets/documents/mesh/mesh.coffee
+++ packages/landsofillusions-assets/documents/mesh/mesh.coffee
@@ -47,10 +47,6 @@
   #   dither: amount of dither used from 0 to 1
   @Meta
     name: @id()
-    fields: =>
-      cameraAngles: [
-        sprite: Document.ReferenceField LOI.Assets.Sprite, [], false
-      ]

   # Store the class name of the visual asset by which we can reach the class by querying LOI.Assets. We can't simply
   # use the name parameter, because in production the name field has a minimized value.


Correct indentation

Correct indentation

--- src/course/model.coffee
+++ src/course/model.coffee
@@ -30,17 +30,19 @@
   cancelJoin: ->   @user.removeCourse(@)

   description: ->
-    status = if @isIncomplete() # still fetching
+    status = \
+      if @isIncomplete() # still fetching
         ""
       else if @isPending() # we originated from a join request
         "#{@to.course.name} #{@to.period.name} period"
       else
         "#{@name} #{_.first(@periods).name} period"
-      teachers = @teacherNames()
-      if status and teachers
-        "#{status} taught by #{teachers}"
-      else
-        status
+
+    teachers = @teacherNames()
+    if status and teachers
+      "#{status} taught by #{teachers}"
+    else
+      status

   teacherNames: ->
     teachers = @teachers or @to?.course.teachers
@@ -69,9 +71,11 @@
       book_uuid: @ecosystem_book_uuid, enrollment_code: inviteCode
     })

-  confirm: ->
+  confirm: (studentId) ->
     api.channel.once "course.#{@id}.receive.confirmation.*", @_onConfirmed
-    api.channel.emit("course.#{@id}.send.confirmation", data: { id: @id })
+    api.channel.emit("course.#{@id}.send.confirmation",
+      data: { id: @id, student_identifier: studentId}
+    )

   _onConfirmed:  ({data}) ->
     if data.to


Remove unnecessary debugging.

Remove unnecessary debugging.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -80,8 +80,7 @@
         if code is 0
           fxn()

-  @deferred: =>
-    console.log 'Who???'
+  @deferred: =>
     resolve = null
     reject = null
     promise = new Promise =>


Newlines and less explosions

Newlines and less explosions

--- resources/assets/coffee/_classes/reply-preview.coffee
+++ resources/assets/coffee/_classes/reply-preview.coffee
@@ -69,14 +69,14 @@
     osu.pageChange()

   hidePreview: (e) =>
-    e.preventDefault()
+    e?.preventDefault()
+
     $(@editBox).removeClass 'hidden'
     $(@previewBox).addClass 'hidden'

     $(@previewButton).removeClass 'js-is-active'
     $(@writeButton).addClass 'js-is-active'

-
     osu.pageChange()

   resetPreview: =>


Removes last names from user source

Removes last names from user source

--- coffee/game/users/source.coffee
+++ coffee/game/users/source.coffee
@@ -1,55 +1,55 @@
 USER_SOURCE = [
   {
-    name: 'Al Pacino',
+    name: 'Al',
     profile_picture_src: 'al_pacino.jpeg',
     location: 'NYC',
     music: 'Johnny Cash, Neil Young',
     likes: 'Chainsaws, having sympathy for the devil'
   },
   {
-    name: 'Robert de Niro',
+    name: 'Robert',
     profile_picture_src: 'de_niro.jpg',
     location: 'NYC',
     music: 'Eric Clapton, Rolling Stones',
     likes: 'Pasta, taxis, Italy <3'
   },
   {
-    name: 'Ewan McGregor',
+    name: 'Ewan',
     profile_picture_src: 'mcgregor.jpg',
     location: 'London',
     music: 'Beatles, Yardbirds',
     likes: 'Fishing, cars, Arrr Scotland'
   },
   {
-    name: 'Natalie Portman',
+    name: 'Natalie',
     profile_picture_src: 'portman.jpg',
     location: 'NYC',
     music: 'Otis Reeding, Phoenix',
     likes: 'Dancing, Anakin, Nicholson'
   },
   {
-    name: 'Morgan Freeman',
+    name: 'Morgan',
     profile_picture_src: 'freeman.jpg',
     location: 'Los Angeles',
     music: 'Chet Baker, Miles Davis',
     likes: 'Mozart, Badgadgets, anyone on Earth'
   },
   {
-    name: 'Logan Green',
+    name: 'Logan',
     profile_picture_src: 'green.jpg',
     location: 'Menlo Park',
     music: 'Chilly Gonzales, Chet Faker',
     likes: 'Vroom, beach, biking'
   },
   {
-    name: 'John Zimmer',
+    name: 'John',
     profile_picture_src: 'zimmer.jpg',
     location: 'Greenwhich',
     music: 'Bob Dylan, CCR',
     likes: 'Rock n roll, Lyft <3'
   },
   {
-    name: 'Kristin Scott Thomas',
+    name: 'Kristin',
     profile_picture_src: 'thomas.jpg',
     location: 'London',
     music: 'Armstrong, Led Zeppelin',


Removed allow method to disallow client side change in configuration

--- collections/remoteConfig.coffee
+++ collections/remoteConfig.coffee
@@ -1,8 +1,4 @@
 @RemoteConfig = new Mongo.Collection 'RemoteConfig'
-RemoteConfig.allow
-  insert: -> true
-  update: -> true
-  remove: -> true

 Meteor.startup ->
   if Meteor.server


Check if User.current is defined. Close #81

--- app/app_view.coffee
+++ app/app_view.coffee
@@ -34,7 +34,7 @@
   initialize: ->
     @$el.html @template()

-    @showBetaDialog() unless User.current.prefs?.dashboard?.beta
+    @showBetaDialog() unless User.current?.prefs?.dashboard?.beta

     @appHeader = new AppHeader({el: @$('.app-header')})
     @dashboardView = new DashboardView


Removing duplicate 'UNLESS'

Removing duplicate 'UNLESS'

--- grammar.coffee
+++ grammar.coffee
@@ -174,7 +174,7 @@
   Existence: [
     o "Expression ?",                           -> new Existence $1
   ]
-
+
   Do: [
     o "DO Code",                                -> $2.do()
   ]
@@ -572,7 +572,7 @@
   ["right",     'WHEN', 'LEADING_WHEN', 'FORIN', 'FOROF', 'FROM', 'TO', 'BY', 'THROW']
   ["right",     'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS']
   ["right",     '=', ':', 'COMPOUND_ASSIGN', 'RETURN']
-  ["right",     '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']
+  ["right",     '->', '=>', 'POST_IF', 'POST_UNLESS']
 ]

 # Wrapping Up


protocol: Return early if no responses are expected

Otherwise end up waiting forever

--- src/protocol.coffee
+++ src/protocol.coffee
@@ -142,14 +142,19 @@
     tests[payload.name] = payload.tests if payload.tests? # not all components have tests
     if responses == expectResponses
       debug 'got all component sources', Object.keys(tests).length
-      client.removeListener 'component', gotComponent
-      return callback null, tests
+      return complete null, tests
+
+  complete = (err, tests) ->
+    client.removeListener 'component', gotComponent
+    return callback err, tests

   getComponents client, (err, components) ->
-    return callback err if err
+    return complete err if err

     componentNames = Object.keys components
     expectResponses = componentNames.length
+    return complete null, tests if expectResponses == 0
+
     debug 'retrieving sources for', expectResponses

     client.on 'component', gotComponent


:art: Rename For Intent

:art: Rename For Intent

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -103,7 +103,7 @@
       unless value?.length
         @suppressForClasses = []
         return
-      @suppressForClasses = _.chain(value).map((s) -> s?.trim().split('.')).compact().value()
+      @suppressForClasses = _.chain(value).map((classNames) -> classNames?.trim().split('.')).compact().value()

     # Handle events from suggestion list
     @subscriptions.add(@suggestionList.onDidConfirm(@confirm))


BUG-FIX [ui-list] Clear list when changed to undefined

--- src/helpers/list.coffee
+++ src/helpers/list.coffee
@@ -45,11 +45,11 @@
     @itemName or= 'item'
     @items = []

-    @watch '_main', (list)->
-      return unless list instanceof Array
-      if @list = list
+    @watch '_main', (@list)->
+      unless @list instanceof Array
+        return @resetItems()
+      else
         @resetItems()
-
         @apply 'listSort' if @scope.listSort
         @apply 'listFilter' if @scope.listFilter

@@ -75,7 +75,7 @@
     @items = []
     @$el.empty()

-    if @list.length
+    if @list?.length
       @add(item) for item in @list
       undefined
     else if @scope.listEmpty and @template


Add more diagnostic logging to addHook()

--- src/hook-manager.coffee
+++ src/hook-manager.coffee
@@ -59,15 +59,16 @@
           url: url
         events: ALL_EVENTS

-      @robot.logger.info "Sending request to add GitHub events hook for #{user}/#{repo} to:
-        https://api.github.com/repos/#{user}/#{repo}/hooks"
+      @robot.logger.info """
+        Sending request to add GitHub events hook for #{user}/#{repo} to: https://api.github.com/repos/#{user}/#{repo}/hooks"
+
+        #{util.inspect(data)}
+        """

       @robot.http("https://api.github.com/repos/#{user}/#{repo}/hooks")
         .header('Accept', 'application/json')
         .header('Authorization', "token #{token}")
         .post(data) (error, response, body) =>
-          @robot.logger.info "Response code from add hook: #{response.status}"
-
           if error
             @robot.logger.error error.toString()
             @message.reply """
@@ -78,7 +79,6 @@
             return

           @robot.logger.info util.inspect(response)
-
           @message.reply "I was able to successfully add the GitHub events hook to #{user}/#{repo}"
     catch e
       @message.reply e.message


Fix handling of xlsx file with only one sheet

Fix bug in `Drawings.subfilename` for single-sheet case

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -826,15 +826,12 @@
         drawing.load data
         drawing
   subfilename: (extension, drawing) ->
+    filename2 = path.parse @filename
+    filename2.base = filename2.base[...-filename2.ext.length]
     if @drawings.length > 1
-      filename2 = path.parse filename ? @filename
-      filename2.base = filename2.base[...-filename2.ext.length]
       filename2.base += @constructor.filenameSeparator + drawing.subname
-      filename2.base += extension
-      path.format filename2
-    else
-      drawing?.filename = @filename  ## use Drawing default if not filename?
-      filename
+    filename2.base += extension
+    path.format filename2
   writeSVG: (mappings, filename) ->
     for drawing in @drawings
       drawing.writeSVG mappings, @subfilename '.svg', drawing


Make svgtiler.js run directly in browser via <script> tag

* Wrap in function to prevent polluting namespace (except `svgtiler`)
* Mock Node's `path` module to the extent needed (extension detection)
* Fix use of `DOMParser` because of CoffeeScript scoping
* Force SVG namespace before parsing, so we get the correct namespaceURI
* Extend `Input.parseFile` API to allow passing in file data directly
* Export all classes, namespaces, `unrecognizedSymbol`, `main` function

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -1,6 +1,6 @@
 `#!/usr/bin/env node
+(function() {
 `
-#require('source-map-support').install()
 unless window?
   path = require 'path'
   fs = require 'fs'
@@ -10,6 +10,11 @@
   XMLSerializer = xmldom.XMLSerializer
   prettyXML = require 'prettify-xml'
   graphemeSplitter = new require('grapheme-splitter')()
+else
+  DOMParser = window.DOMParser # escape CoffeeScript scope
+  path =
+    extname: (x) -> /\.[^/]+$/.exec(x)[0]
+    dirname: (x) -> /[^]*\/|/.exec(x)[0]

 SVGNS = 'http://www.w3.org/2000/svg'
 XLINKNS = 'http://www.w3.org/1999/xlink'
@@ -179,6 +184,15 @@
     super()
     for own key, value of options
       @[key] = value
+    ## Force SVG namespace when parsing, so nodes have correct namespaceURI.
+    ## (This is especially important on the browser, so the results can be
+    ## reparented into an HTML Document.)
+    @svg = @svg.replace /^\s*<(?:[^<>'"\/]|'[^']*'|"[^"]*")*\s*(\/?\s*>)/,
+      (match, end) ->
+        unless 'xmlns' in match
+          match = match[...match.length-end.length] +
+            " xmlns='#{SVGNS}'" + match[match.length-end.length..]
+        match
     @xml = new DOMParser
       locator:  ## needed when specifying errorHandler
         line: 1
@@ -290,18 +304,19 @@

 class Input
   @encoding: 'utf8'
-  @parseFile: (filename) ->
+  @parseFile: (filename, filedata) ->
     ## Generic method to parse file once we're already in the right class.
     input = new @
     input.filename = filename
-    input.parse fs.readFileSync filename,
-      encoding: @encoding
+    unless filedata?
+      filedata = fs.readFileSync filename, encoding: @encoding
+    input.parse filedata
     input
-  @recognize: (filename) ->
+  @recognize: (filename, filedata) ->
     ## Recognize type of file and call corresponding class's `parseFile`.
     extension = extensionOf filename
     if extension of extensionMap
-      extensionMap[extension].parseFile filename
+      extensionMap[extension].parseFile filename, filedata
     else
       throw new SVGTilerException "Unrecognized extension in filename #{filename}"

@@ -873,11 +888,15 @@
     console.log 'Not enough filename arguments'
     help()

-exports =
-  Symbol: Symbol
-  Drawing: Drawing
+exports = {Symbol, StaticSymbol, DynamicSymbol, unrecognizedSymbol,
+  Mapping, ASCIIMapping, JSMapping, CoffeeMapping,
+  Drawing, ASCIIDrawing, DSVDrawing, SSVDrawing, CSVDrawing, TSVDrawing,
+  Drawings, XLSXDrawings,
+  Input, Mappings, Context, SVGTilerException, SVGNS, XLINKNS, main}
 module?.exports ?= exports
 window?.svgtiler ?= exports

 unless window?
   main()
+
+`}).call(this)`


Build command and show results

Build command and show results

--- src/tankipas.coffee
+++ src/tankipas.coffee
@@ -13,8 +13,9 @@
 tankipas = require "commander"
 fs = require "fs"
 path = require "path"
+exec = require( "child_process" ).exec
+which = require( "which" ).sync
 chalk = require "chalk"
-which = require( "which" ).sync
 error = chalk.bold.red
 success = chalk.bold.green

@@ -59,3 +60,33 @@
 # --- get user

 sUser = tankipas.user ? no
+
+# --- build command
+
+sUserFilter = ""
+sUserFilter = "-u #{ sUser }" if sUser and sSystem is "hg"
+sUserFilter = "--author #{ sUser }" if sUser and sSystem is "git"
+
+sCommand = "#{ sSystem } log #{ sUserFilter }"
+
+# --- exec command
+
+exec sCommand, { maxBuffer: 1048576 }, ( oError, sStdOut, sStdErr ) ->
+    if oError
+        console.log error "✘ #{ oError }."
+        process.exit 1
+    iTotal = 0
+    iPrevStamp = null
+    iGap *= 60000
+    sDateFilter = if sSystem is "git" then "Date:" else "date:"
+    for sLine in sStdOut.split( require( "os" ).EOL ).reverse()
+        if sLine.search( sDateFilter ) isnt -1
+            iCurrentStamp = ( new Date( sLine.substr( sDateFilter ).trim() ) ).getTime()
+            iTotal += iDifference if iPrevStamp and iPrevStamp < iCurrentStamp and ( iDifference = iCurrentStamp - iPrevStamp ) < iGap
+            iPrevStamp = iCurrentStamp
+    iTotal /= 1000
+    iMinutes = if ( iMinutes = Math.floor( iTotal / 60 ) ) > 60 then Math.floor( iMinutes / 60 ) % 60 else iMinutes
+    iHours = Math.floor iTotal / 3600
+    sUserString = if sUser then " (for #{ chalk.cyan( sUser ) })" else ""
+    console.log chalk.green( "✔" ), "Time elapsed on project#{ sUserString }: ±#{ chalk.yellow( iHours ) } hours & #{ chalk.yellow( iMinutes ) } minutes."
+    process.exit 0


Set view to object if provided as option

--- src/template.coffee
+++ src/template.coffee
@@ -28,9 +28,13 @@

 class Template extends EventEmitter
-    constructor: (view, opts = {}, template) ->
+    constructor: (opts = {}, template) ->
         # options
         [template, opts] = [opts, {}] if typeof opts is 'function'
+        view = null
+        if typeof(opts.view) is not "undefined"
+            view = opts.view
+            opts.view = null
         # defaults
         opts.encoding ?= 'utf-8'
         opts.doctype ?= off
@@ -53,7 +57,7 @@
         # instantiate
         @xml = new ExtendedBuilder opts
         @xml.template = this
-        @xml.view     = view
+        @xml.view     = view
         # override query
         old_query = @xml.query
         @xml.query = (type, tag, key) ->


fix(spellchecker): Preliminary Linux support

The Linux component of Atom's spellchecker depends on Hunspell for
corrections. It requires a dictionary to be set, unlike Mac OS X
and Windows that have spellcheckers built in to the OS, and they
have the dictionaries built-in as well. This commit uses Atom's
spellchecker default dictionary method (which I wish was a public
API) to reset the dictionary to its default setting. It also ignores
the dictionaries available command for the Linux platform, which
seems to always return an empty Array.

--- src/nylas-spellchecker.coffee
+++ src/nylas-spellchecker.coffee
@@ -1,3 +1,5 @@
+path = require('path')
+
 spellchecker = require('spellchecker')

 class NylasSpellchecker
@@ -14,9 +16,9 @@

   isSpelledCorrectly: (args...) => not @isMisspelled(args...)

-  setLanguage: (lang="") ->
+  setLanguage: (lang="", dict=@_getDictionaryPath()) ->
     @languageAvailable = @isLanguageAvailable(lang)
-    if @languageAvailable
+    if @languageAvailable or process.platform is 'linux'
       spellCheck = @isSpelledCorrectly
     else
       spellCheck = -> true
@@ -26,26 +28,41 @@
     if lang.length is 0 then lang = "en-US"

     @_setWebframeSpellchecker(lang, spellCheck)
-    spellchecker.setDictionary(lang)
+    spellchecker.setDictionary(lang, dict)

   # Separate method for testing
   _setWebframeSpellchecker: (lang, spellCheck) ->
     require('web-frame').setSpellCheckProvider(lang, false, {spellCheck})

+  # node-spellchecker's method for resolving the builtin hunspell dictionaries for Linux
+  # (From https://github.com/atom/node-spellchecker/blob/master/lib/spellchecker.js#L50-L61)
+  _getDictionaryPath: ->
+    dict = path.join(require.resolve('spellchecker'), '..', '..', 'vendor', 'hunspell_dictionaries')
+    try
+      # HACK: Special case being in an asar archive
+      unpacked = dict.replace('.asar' + path.sep, '.asar.unpacked' + path.sep);
+      if require('fs').statSyncNoException(unpacked)
+        dict = unpacked;
+    catch
+
+    dict
+
   #### spellchecker methods ####
-  setDictionary: (lang) => @setLanguage(lang)
+  setDictionary: (lang, dict) => @setLanguage(lang, dict)

   add: spellchecker.add

   isMisspelled: (text) =>
-    if @languageAvailable then spellchecker.isMisspelled(text)
-    else return false
+    if @languageAvailable or process.platform is 'linux'
+      spellchecker.isMisspelled(text)
+    else
+      return false

   getAvailableDictionaries: ->
     spellchecker.getAvailableDictionaries() ? []

   getCorrectionsForMisspelling: (args...) =>
-    if @languageAvailable
+    if @languageAvailable or process.platform is 'linux'
       spellchecker.getCorrectionsForMisspelling(args...)
     else return []


set title of <C-F12> dialog to "RIDE Protocol Log"

--- init-nw.coffee
+++ init-nw.coffee
@@ -149,32 +149,27 @@
   $(document).keydown (e) ->
     if e.which == 123 && !e.altKey # F12
       if     !e.ctrlKey && !e.shiftKey then nww.showDevTools().setAlwaysOnTop 1; false
-      else if e.ctrlKey && !e.shiftKey then showProxyLog(); false
+      else if e.ctrlKey && !e.shiftKey then showProtocolLog(); false
       else if e.ctrlKey &&  e.shiftKey then foo.bar # cause a crash

-  showProxyLog = ->
-    lw = open ''
-    lw.document.write '''
-      <html>
-        <head>
-          <title>Proxy Log</title>
-          <style>body{font-family:monospace;white-space:pre}</style>
-          <script></script>
-        </head>
-        <body></body>
-      </html>
-    '''
+  showProtocolLog = ->
+    window.lw = lw = open 'empty.html'
     wr = (s) ->
       if !lw || lw.closed || !lw.document || !lw.document.createTextNode
-        i = proxy.log.listeners.indexOf wr
-        if i >= 0 then proxy.log.listeners.splice i, 1; lw = null
+        i = proxy.log.listeners.indexOf wr; i >= 0 && proxy.log.listeners.splice i, 1; lw = null
       else
-        b = lw.document.body
-        atEnd = b.scrollTop == b.scrollHeight - b.clientHeight
-        b.appendChild lw.document.createTextNode s
-        if atEnd then b.scrollTop = b.scrollHeight - b.clientHeight
+        b = lw.document.body; atEnd = b.scrollTop == b.scrollHeight - b.clientHeight
+        b.appendChild lw.document.createTextNode s; if atEnd then b.scrollTop = b.scrollHeight - b.clientHeight
       return
-    wr proxy.log.get().join ''; proxy.log.listeners.push wr
+    lw.onload = ->
+      lw.document.body.innerHTML = '''
+        <style>
+          body{font-family:monospace;margin:0;padding:0;white-space:pre;
+               position:absolute;top:0;bottom:0;left:0;right:0;overflow:scroll}
+        </style>
+      '''
+      lw.document.title = 'RIDE Protocol Log'; wr proxy.log.get().join ''; proxy.log.listeners.push wr
+      return
     false

   # expandStackString inserts snippets of code next to each file:///filename.js:123:45


emit uploading

emit uploading

--- src/uploader.coffee
+++ src/uploader.coffee
@@ -92,7 +92,6 @@
         @finishJob()
       else
         # for race condition about failed parts
-        console.error @parts.length
         if @parts.length > 0
           @uploadChunks()
         else
@@ -134,6 +133,8 @@

         chunk.progress = true
         chunk.client ?= @getNewClient()
+
+        @emit 'uploading', chunk.partNumber

         chunk.client.uploadPart
           Body:       chunk


Fix sorting

Fix sorting

Was bugged when there were more than 10 projects listed

--- lib/project-manager-view.coffee
+++ lib/project-manager-view.coffee
@@ -90,4 +90,7 @@

   sortBy: (arr, key) ->
     arr.sort (a, b) ->
-      (a[key] || '\uffff').toUpperCase() > (b[key] || '\uffff').toUpperCase()
+      a = (a[key] || '\uffff').toUpperCase()
+      b = (b[key] || '\uffff').toUpperCase()
+
+      return if a > b then 1 else -1


forgot to fix zero-rows bug when auto-transpose

--- lib/slickgrid/table-view.coffee
+++ lib/slickgrid/table-view.coffee
@@ -14,7 +14,7 @@
         @emitter = new Emitter()
         @normalData = @data
         @normalColumns = @columns
-        if @options.transpose
+        if @options.transpose and @data
             @transposeData()
         else
             @deTransposeData()


Web link index is parsed from web link closes #108

--- wzk/ui/grid/PaneMode.coffee
+++ wzk/ui/grid/PaneMode.coffee
@@ -20,7 +20,6 @@
   ###
   @ATTRS:
     LINK: 'webLink'
-    INDEX: 'webLinkI'

   ###*
     @enum {string}
@@ -75,13 +74,23 @@
     @ss.set wzk.ui.grid.PaneMode.PARAM, model['id']
     A = wzk.ui.grid.PaneMode.ATTRS
     for el in @dom.clss wzk.ui.grid.PaneMode.SNIPPET.CLS
-      link = goog.dom.dataset.get el, A.LINK
-      url = model['_web_links'][link]
-      i = goog.dom.dataset.get el, A.INDEX
-      if i?
-        url = url[i]
+      link = @parseLink String goog.dom.dataset.get(el, A.LINK)
+      url = model['_web_links'][url.link]
+
+      if link.index?
+        url = url[link.index]
       if url?
         @processSnippet url, el
+
+  ###*
+    @protected
+    @param {string} attr
+    @return {Object}
+  ###
+  parseLink: (attr) ->
+    tokens = attr.split '.'
+    link: tokens[0]
+    index: (if tokens[1]? then tokens[1] else null)

   ###*
     @protected


Only measure both dimensions if a video

Only measure both dimensions if a video

Closes #12

--- src/viewport.coffee
+++ src/viewport.coffee
@@ -133,7 +133,9 @@
 			@windowWidth = window.innerWidth
 			@updateContainerSize() if @shouldWatchComponentSize

-		# Update the container size
+		# Update the container size.  Note, if there is no video specified we don't
+		# need to know the height.  This saves some CPU:
+		# https://jsperf.com/does-reading-one-offset-improve-performance
 		updateContainerSize: ->
 			@containerWidth = @$el.offsetWidth
-			@containerHeight = @$el.offsetHeight
+			@containerHeight = @$el.offsetHeight if @video


We cannot change  if the element in not null in  + clean code + fix regex for multiple lines comment -- Related to #1212 and #1214

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -703,7 +703,6 @@
             is_parsing_string = false
             to_skip = 0
             result = 0
-            start = 0

             for char, i in query
                 if to_skip > 0 # Because we cannot mess with the iterator in coffee-script
@@ -727,12 +726,10 @@
                     result_inline_comment = @regex.inline_comment.exec query.slice i
                     if result_inline_comment?
                         to_skip = result_inline_comment[0].length-1
-                        start += result_inline_comment[0].length
                         continue
                     result_multiple_line_comment = @regex.multiple_line_comment.exec query.slice i
                     if result_multiple_line_comment?
                         to_skip = result_multiple_line_comment[0].length-1
-                        start += result_multiple_line_comment[0].length
                         continue

             return result
@@ -756,7 +753,6 @@
             is_parsing_string = false
             to_skip = 0
             result = 0
-            start = 0

             for char, i in query
                 if to_skip > 0 # Because we cannot mess with the iterator in coffee-script
@@ -780,12 +776,10 @@
                     result_inline_comment = @regex.inline_comment.exec query.slice i
                     if result_inline_comment?
                         to_skip = result_inline_comment[0].length-1
-                        start += result_inline_comment[0].length
                         continue
                     result_multiple_line_comment = @regex.multiple_line_comment.exec query.slice i
                     if result_multiple_line_comment?
                         to_skip = result_multiple_line_comment[0].length-1
-                        start += result_multiple_line_comment[0].length
                         continue

             return result
@@ -1266,7 +1260,7 @@
             semicolon: /^(\s)*;(\s)*/
             number: /^[0-9]+\.?[0-9]*/
             inline_comment: /^(\s)*\/\/.*\n/
-            multiple_line_comment: /^(\s)*\/\*[^]*\*\//
+            multiple_line_comment: /^(\s)*\/\*[^(\*\/)]*\*\//
             get_first_non_white_char: /\s*(\S+)/
             last_char_is_white: /.*(\s+)$/
         stop_char: # Just for performance (we look for a stop_char in constant time - which is better than having 3 and conditions) and cleaner code
@@ -1354,18 +1348,20 @@
                             element.type = 'string'
                             start = i
                         continue
-                    result_inline_comment = @regex.inline_comment.exec query.slice i
-                    if result_inline_comment?
-                        to_skip = result_inline_comment[0].length-1
-                        start += result_inline_comment[0].length
-                        continue
-                    result_multiple_line_comment = @regex.multiple_line_comment.exec query.slice i
-                    if result_multiple_line_comment?
-                        to_skip = result_multiple_line_comment[0].length-1
-                        start += result_multiple_line_comment[0].length
-                        continue
+

                     if element.type is null
+                        result_inline_comment = @regex.inline_comment.exec query.slice i
+                        if result_inline_comment?
+                            to_skip = result_inline_comment[0].length-1
+                            start += result_inline_comment[0].length
+                            continue
+                        result_multiple_line_comment = @regex.multiple_line_comment.exec query.slice i
+                        if result_multiple_line_comment?
+                            to_skip = result_multiple_line_comment[0].length-1
+                            start += result_multiple_line_comment[0].length
+                            continue
+
                         if start is i
                             result_white = @regex.white_start.exec query.slice i
                             if result_white?
@@ -2333,7 +2329,6 @@
                 full_query += @queries[@index]

                 try
-                    console.log full_query
                     rdb_query = @evaluate(full_query)
                 catch err
                     @.$('.loading_query_img').hide()
@@ -2518,7 +2513,6 @@
             for char, i in query
                 if to_skip > 0
                     to_skip--
-                    start++
                     continue

                 if is_parsing_string is true
@@ -2537,15 +2531,17 @@

                     result_inline_comment = @regex.inline_comment.exec query.slice i
                     if result_inline_comment?
-                        result_query += query.slice(start, i).replace(/\n/g, '\\\\n')
+                        result_query += query.slice(start, i).replace(/\n/g, '')
                         start = i
-                        to_skip = result_inline_comment[0].length
+                        to_skip = result_inline_comment[0].length-1
+                        start += result_inline_comment[0].length
                         continue
                     result_multiple_line_comment = @regex.multiple_line_comment.exec query.slice i
                     if result_multiple_line_comment?
                         result_query += query.slice(start, i).replace(/\n/g, '\\\\n')
                         start = i
-                        to_skip = result_multiple_line_comment[0].length
+                        to_skip = result_multiple_line_comment[0].length-1
+                        start += result_multiple_line_comment[0].length
                         continue
             if is_parsing_string
                 result_query += query.slice(start, i).replace(/\n/g, '\\\\n')


Remove old code used with forceRun

Remove old code used with forceRun

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -243,7 +243,6 @@
                 container: @
                 on_success: @success_on_connect
                 on_fail: @error_on_connect
-                callback: @rdb_global_callback

             # One callback to rule them all
             $(window).mousemove @handle_mousemove
@@ -2481,10 +2480,6 @@
             @container = args.container
             @on_success = args.on_success
             @on_fail = args.on_fail
-            @callback = (error, cursor) ->
-                that.done++
-                if that.done is that.count
-                    args.callback error, cursor #rdb_global_callback()

             if window.location.port is ''
                 if window.location.protocol is 'https:'


Fix bug for history + clean code

Fix bug for history + clean code

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -684,13 +684,13 @@
                 else if event.type is 'keyup' and event.altKey and event.which is 38 # Key up
                     if @history_displayed_id < @history.length
                         @history_displayed_id++
-                        @codemirror.setValue @history[@history.length-@history_displayed_id]
+                        @codemirror.setValue @history[@history.length-@history_displayed_id].query
                         event.preventDefault()
                         return true
                 else if event.type is 'keyup' and event.altKey and event.which is 40 # Key down
                     if @history_displayed_id > 1
                         @history_displayed_id--
-                        @codemirror.setValue @history[@history.length-@history_displayed_id]
+                        @codemirror.setValue @history[@history.length-@history_displayed_id].query
                         event.preventDefault()
                         return true
                     else if @history_displayed_id is 1
@@ -699,35 +699,15 @@
                         @codemirror.setCursor @codemirror.lineCount(), 0 # We hit the draft and put the cursor at the end
                 else if event.type is 'keyup' and event.altKey and event.which is 33 # Page up
                     @history_displayed_id = @history.length
-                    @codemirror.setValue @history[@history.length-@history_displayed_id]
+                    @codemirror.setValue @history[@history.length-@history_displayed_id].query
                     event.preventDefault()
                     return true
                 else if event.type is 'keyup' and event.altKey and event.which is 34 # Page down
                     @history_displayed_id = @history.length
-                    @codemirror.setValue @history[@history.length-@history_displayed_id]
+                    @codemirror.setValue @history[@history.length-@history_displayed_id].query
                     @codemirror.setCursor @codemirror.lineCount(), 0 # We hit the draft and put the cursor at the end
                     event.preventDefault()
                     return true
-
-            query_before_cursor = @codemirror.getRange {line: 0, ch: 0}, @codemirror.getCursor()
-            query_after_cursor = @codemirror.getRange @codemirror.getCursor(), {line:@codemirror.lineCount()+1, ch: 0}
-
-            # Initialize @current_element, which tracks what the user typed before they hit TAB (to auto-complete).
-            # Tracking this helps us let the user loop over all the suggestions available for the fragment they typed (and go back to the fragment).
-            # We now compute this stack earlier, because we need it for @pair_char
-            # BUT we don't want to save data too early
-            previous_element = @current_element
-            @current_element = ''
-
-            stack = @extract_data_from_query
-                query: query_before_cursor
-                position: 0
-
-            @pair_char(event, stack)
-
-            current_element = @current_element
-            @current_element = previous_element
-
             # If there is a hilighted suggestion, we want to catch enter
             if @$('.suggestion_name_li_hl').length > 0
                 if event?.which is 13
@@ -748,8 +728,16 @@
                 @history_displayed_id = 0
                 @draft = @codemirror.getValue()

-            if event?.which isnt 9 # has to be before create_suggestion()
-                @cursor_for_auto_completion = @codemirror.getCursor()
+            query_before_cursor = @codemirror.getRange {line: 0, ch: 0}, @codemirror.getCursor()
+            query_after_cursor = @codemirror.getRange @codemirror.getCursor(), {line:@codemirror.lineCount()+1, ch: 0}
+
+            # Compute the structure of the query written by the user.
+            # We compute it earlier than before because @pair_char also listen on keydown and needs stack
+            stack = @extract_data_from_query
+                query: query_before_cursor
+                position: 0
+
+            @pair_char(event, stack) # Pair brackets/quotes

             # We just look at key up so we don't fire the call 3 times
             if event?.type? and event.type isnt 'keyup' and event.which isnt 9 and event.type isnt 'mouseup'
@@ -768,7 +756,6 @@

             # Valid step, let's save the data
             @query_last_part = query_after_cursor
-            @current_element = current_element

             # If a selection is active, we just catch shift+enter
             if @codemirror.getSelection() isnt ''
@@ -784,6 +771,10 @@
                     return false
                 else
                     return true
+
+            @current_suggestions = []
+            @current_element = ''
+            @cursor_for_auto_completion = @codemirror.getCursor()

             result =
                 status: null
@@ -808,7 +799,6 @@
                 query: query_before_cursor
                 result: result

-            @current_suggestions = []
             if result.suggestions?.length > 0
                 for suggestion, i in result.suggestions
                     @current_suggestions.push suggestion


Fix race condition in table progress query

Fixes #3721

--- admin/static/coffee/tables/table.coffee
+++ admin/static/coffee/tables/table.coffee
@@ -618,13 +618,14 @@
                 @interval_progress = setInterval @fetch_progress, 1000

         fetch_progress: =>
-            fetch_for_progress = []
+            build_in_progress = false
             for index in @collection.models
                 if index.get('ready') isnt true
-                    fetch_for_progress.push index.get 'index'
-
-            if fetch_for_progress.length > 0
-                query = r.db(@model.get('db')).table(@model.get('name')).indexStatus(r.args(fetch_for_progress))
+                    build_in_progress = true
+                    break
+
+            if build_in_progress and @model.get('db')?
+                query = r.db(@model.get('db')).table(@model.get('name')).indexStatus()
                     .pluck('index', 'ready', 'blocks_processed', 'blocks_total')
                     .merge( (index) => {
                         id: index("index")


Added floor of milliseconds to comply ti sql DATETIME - to discuss.

--- json_utils.coffee
+++ json_utils.coffee
@@ -22,7 +22,10 @@
     return value unless value
     if _.isDate(value)
       try
-        return value.toISOString()
+        # drop milliseconds for mysql DATETIME. TODO: determine whether this is necessary
+        date = moment.utc(value)
+        date.millisecond(0)
+        return date.toDate().toISOString()
       catch e
         return null # not a valid date
     else if _.isString(value)


Revert "re-run tests on changes in site.js"

This reverts commit 89e3a3d26be72fc3d49a89fc8cbc75abf73999ce.

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -4,7 +4,6 @@
     frameworks: [ 'jasmine' ]
     files: [
       'public/vendor/zepto-1.1.4.js'
-      'public/site.js'
       'spec/**/*.coffee'
     ]
     exclude: []


Add timezones to everything else. I need to refactor this

--- src/worldcup.coffee
+++ src/worldcup.coffee
@@ -33,8 +33,13 @@
         else
           msg.send "There are no matches today"

-  robot.respond /(worldcup|wc)( tomorrow)/i, (msg) ->
-    msg.http("http://worldcup2014bot.herokuapp.com/matches/tomorrow")
+  robot.respond /(worldcup|wc)( tomorrow)( [\w \(\&\)]+)?/i, (msg) ->
+    timezone = if msg.match[3]
+      msg.match[3].trim()
+    else
+      ""
+
+    msg.http("http://worldcup2014bot.herokuapp.com/matches/tomorrow?timezone=#{timezone}")
       .get() (err, res, body) ->
         matches = JSON.parse(body).matches
         if matches.length > 0
@@ -85,8 +90,13 @@
       msg.send "http://img.thesun.co.uk/aidemitlum/archive/01689/red_main_1689473a.jpg"
       msg.send "#{person}: you're out"

-  robot.respond /(worldcup|wc)( recap)/i, (msg) ->
-    msg.http("http://worldcup2014bot.herokuapp.com/scores/recap")
+  robot.respond /(worldcup|wc)( recap)( [\w \(\&\)]+)?/i, (msg) ->
+    timezone = if msg.match[3]
+      msg.match[3].trim()
+    else
+      ""
+
+    msg.http("http://worldcup2014bot.herokuapp.com/scores/recap?timezone=#{timezone}")
       .get() (err, res, body) ->
         scores = JSON.parse(body).scores
         if scores.length > 0
@@ -99,8 +109,13 @@
         else
           msg.send "There were no matches yesterday :("

-  robot.respond /(worldcup|wc)( score)/i, (msg) ->
-    msg.http("http://worldcup2014bot.herokuapp.com/scores/now")
+  robot.respond /(worldcup|wc)( score)( [\w \(\&\)]+)?/i, (msg) ->
+    timezone = if msg.match[3]
+      msg.match[3].trim()
+    else
+      ""
+
+    msg.http("http://worldcup2014bot.herokuapp.com/scores/now?timezone=#{timezone}")
       .get() (err, res, body) ->
         score = JSON.parse(body).score
         if score


Allow bold and italic markup to span multiple lines

Fixes #61.

--- grammars/restructuredtext.cson
+++ grammars/restructuredtext.cson
@@ -106,19 +106,21 @@
 			# Bold
 			{
 				name: "markup.bold.restructuredtext"
-				match: "(\\*\\*)[^\\*\\s][^*]*(\\*\\*)"
-				captures:
-					1: "name": "punctuation.definition.bold.restructuredtext"
-					2: "name": "punctuation.definition.bold.restructuredtext"
+				begin: "\\*\\*(?=[^\\*\\s])"
+				end:   "\\*\\*|^(?=\\s*$)"
+				beginCaptures: 0: "name": "punctuation.definition.bold.begin.restructuredtext"
+				endCaptures:   0: "name": "punctuation.definition.bold.end.restructuredtext"
+				patterns: [include: "#inlines"]
 			}

 			# Italic
 			{
 				name: "markup.italic.restructuredtext"
-				match: "(?<!\\\\)(\\*)[^\\*\\s][^*]*(?<!\\\\)(\\*)"
-				captures:
-					1: name: "punctuation.definition.italic.restructuredtext"
-					2: name: "punctuation.definition.italic.restructuredtext"
+				begin: "(?<!\\\\)\\*(?=[^\\*\\s])"
+				end:   "(?<!\\\\)\\*|^(?=\\s*$)"
+				beginCaptures: 0: name: "punctuation.definition.italic.begin.restructuredtext"
+				endCaptures:   0: name: "punctuation.definition.italic.end.restructuredtext"
+				patterns: [include: "#inlines"]
 			}
 		]

@@ -128,8 +130,8 @@
 		name: "markup.raw.restructuredtext"
 		begin: "``"
 		end:   "``((?=[^`\\w\\d])|$)"
-		captures:
-			0: name: "punctuation.definition.raw.restructuredtext"
+		beginCaptures: 0: name: "punctuation.definition.raw.begin.restructuredtext"
+		endCaptures:   0: name: "punctuation.definition.raw.end.restructuredtext"


Search for coffee or js files.

Search for coffee or js files.

This is a bug on npm since it needs to search for js file in prod.

--- src/pry/commands/index.coffee
+++ src/pry/commands/index.coffee
@@ -1,7 +1,7 @@
 fs = require 'fs'

 for file in fs.readdirSync(__dirname)
-  if file.match(/\.coffee$/) && !file.match(/index\.coffee/)
+  if file.match(/\.(coffee|js)$/) && !file.match(/index\.(js|coffee)/)
     file = file.substr 0, file.indexOf('.')
     name = file.substring(0, 1).toUpperCase() + file.substring(1)
     exports[name] = require("./#{file}")


minor changes

minor changes

--- library/exposure/watched.coffee
+++ library/exposure/watched.coffee
@@ -72,9 +72,9 @@
         assert l = lst = "server connection has been lost"
         assert s = srv = "exception occured on the server"
         assert r = rcn = "attempting to restore connection"
-        pos = positionClass: "toast-top-left", closable: no
-        ntm = timeOut: 0, extendedTimeOut: 0, tapToDismiss: no
-        xtm = timeOut: 3000, extendedTimeOut: 1000 # see duplex
+        pos = positionClass: "toast-top-left", closable: null
+        ntm = timeOut: 0, extendedTimeOut: 0, tapToDismiss: 0
+        xtm = timeOut: 3000, extendedTimeOut: 1000 # a duplex
         assert _.extend object, pos for object in [ntm, xtm]
         socket.on "connect", -> $(".toast-warning").remove()
         recon = _.debounce (-> toastr.info r, null, xtm), 500


move slider init into setupForm method

move slider init into setupForm method


Use custom document to create test elements

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -109,17 +109,20 @@
     # Simulate an atom-text-editor element attached to a atom-workspace element attached
     # to a body element that has the same classes as the current body element.
     unless @testEditor?
-      testBody = document.createElement('body')
+      # Use new document so that custom elements don't actually get created
+      testDocument = document.implementation.createDocument(document.namespaceURI, 'html')
+
+      testBody = testDocument.createElement('body')
       testBody.classList.add(@classesForElement(document.body)...)

-      testWorkspace = document.createElement('atom-workspace')
+      testWorkspace = testDocument.createElement('atom-workspace')
       workspaceClasses = @classesForElement(document.body.querySelector('atom-workspace'))
       workspaceClasses = ['workspace'] if workspaceClasses.length is 0
       testWorkspace.classList.add(workspaceClasses...)

       testBody.appendChild(testWorkspace)

-      @testEditor = document.createElement('atom-text-editor')
+      @testEditor = testDocument.createElement('atom-text-editor')
       @testEditor.classList.add('editor')
       testWorkspace.appendChild(@testEditor)


fix(timer): invalid syntax

fix(timer): invalid syntax

--- app/assets/javascripts/app/timer.coffee
+++ app/assets/javascripts/app/timer.coffee
@@ -26,7 +26,7 @@
   visibilitychange: (evt) =>
     now = new Date().getTime()
     console.log('Page View')
-    fetchInterval: 15 * (60 * 1000) #only fetch after 15 mins.
+    fetchInterval = 15 * (60 * 1000) #only fetch after 15 mins.
     if now - @lastVisibilityTrigger > fetchInterval
       @lastVisibilityTrigger = now
       console.log('Reloading Running Timer')


Dont use inline css sourcemaps - write them as *.map files (just as in scripts task)

--- gulp/tasks/build/styles.coffee
+++ gulp/tasks/build/styles.coffee
@@ -21,7 +21,7 @@
     .pipe(sourcemaps.init())
       .pipe(sass())
       .pipe(gulpIf(!!+GLOBALS.COMPRESS_ASSETS, minifyCSS(processImport: false)))
-    .pipe(sourcemaps.write())
+    .pipe(sourcemaps.write('./'))

     .on('error', notify.onError((error) -> error.message))
     .pipe(gulp.dest(DESTINATIONS.styles))


fix: duration counter

fix: duration counter


fix toggle of recurring form for absences turbolinks executes jQuery.ready with every request, click event listeners were cumulated -> fix by clearing the event listeners

--- app/assets/javascripts/layout.js.coffee
+++ app/assets/javascripts/layout.js.coffee
@@ -14,11 +14,12 @@
     timeFormat: 'HH:mm'
   })

-
   # Toggler
+  # make sure we have only one toggle listener active
+  # (turbolinks will execute jQuery.ready with every request)
+  $(document).off 'click', '.toggle'
   $(document).on 'click', '.toggle', (element) ->
     $('#' + $(this).data('toggle-target')).toggle()
-

   # Pickadate
   window.init_pickdate = () ->


fix(composer): Do not unregister composer hot windows when a composer is closed

--- internal_packages/composer/lib/main.cjsx
+++ internal_packages/composer/lib/main.cjsx
@@ -44,15 +44,15 @@

 module.exports =
   activate: (@state={}) ->
-    atom.registerHotWindow
-      windowType: 'composer'
-      replenishNum: 2
-
-    # Register our composer as the app-wide Composer
+    # Register our composer as the window-wide Composer
     ComponentRegistry.register ComposerView,
       role: 'Composer'

     if atom.isMainWindow()
+      atom.registerHotWindow
+        windowType: 'composer'
+        replenishNum: 2
+
       ComponentRegistry.register ComposeButton,
         location: WorkspaceStore.Location.RootSidebar.Toolbar
     else
@@ -64,7 +64,8 @@
         location: WorkspaceStore.Location.Center

   deactivate: ->
-    atom.unregisterHotWindow('composer')
+    if atom.isMainWindow()
+      atom.unregisterHotWindow('composer')
     ComponentRegistry.unregister(ComposerView)
     ComponentRegistry.unregister(ComposeButton)
     ComponentRegistry.unregister(ComposerWithWindowProps)


Only load CKEDITOR when contenteditable elements present

--- app/assets/javascripts/papers.js.coffee
+++ app/assets/javascripts/papers.js.coffee
@@ -6,7 +6,7 @@
       $('<li class="author">').appendTo $('ul.authors')

 $(document).ready ->
-  if $('[contenteditable!=false]').length > 0
+  if $("[contenteditable]").length > 0
     for elementId in ['body_editable', 'abstract_editable']
       CKEDITOR.inline elementId,
         extraPlugins: 'sharedspace'


Add effect to disable an input.

Add effect to disable an input.

--- app/assets/javascripts/pwn-fx.js.coffee
+++ app/assets/javascripts/pwn-fx.js.coffee
@@ -404,6 +404,51 @@
 PwnFx.registerEffect 'hide', PwnFxHide

+# Disables / enables elements when an element is clicked or checked / unchecked.
+#
+# Attributes:
+#   data-pwnfx-disable: a name for the events caused by this element's triggering
+#   data-pwnfx-disable-trigger: "click" means events are triggered when the
+#       element is clicked, "checked" means events are triggered when the
+#       element is checked; (default: click)
+#   data-pwnfx-disable-positive: set to the same value as data-pwnfx-disable on
+#       elements that will be disabled when a positive event (click / check) is
+#       triggered, and enabled when a negative event (uncheck) is triggered
+#   data-pwnfx-disable-negative: set to the same value as data-pwnfx-disable on
+#       elements that will be enabled when a positive event (click / check) is
+#       triggered, and disabled when a negative event (uncheck) is triggered
+class PwnFxDisable
+  constructor: (element, identifier, scopeId) ->
+    trigger = element.getAttribute('data-pwnfx-disable-trigger') || 'click'
+    positiveSelector = "[data-pwnfx-disable-positive=\"#{identifier}\"]"
+    negativeSelector = "[data-pwnfx-disable-negative=\"#{identifier}\"]"
+    onChange = (event) ->
+      positive = (trigger is 'click') || element.checked
+      disableSelector = if positive then positiveSelector else negativeSelector
+      enableSelector = if positive then negativeSelector else positiveSelector
+
+      scope = PwnFx.resolveScope scopeId, element
+      for targetElement in PwnFx.queryScope(scope, disableSelector)
+        targetElement.disabled = true
+      for targetElement in PwnFx.queryScope(scope, enableSelector)
+        targetElement.disabled = false
+      if trigger is 'click'
+        event.preventDefault()
+        false
+      else
+        true
+
+    if trigger is 'click'
+      element.addEventListener 'click', onChange, false
+    else if trigger is 'checked'
+      element.addEventListener 'change', onChange, false
+      onChange()
+    else
+      throw new Error("Unimplemented pwnfx-disable trigger #{trigger}")
+
+PwnFx.registerEffect 'disable', PwnFxDisable
+
+
 # Shows / hides elements in a DOM depending on an input's value.
 #
 # Attributes:


move object in fb share up a level...

move object in fb share up a level...

--- app/assets/javascripts/social.js.coffee
+++ app/assets/javascripts/social.js.coffee
@@ -41,13 +41,11 @@
     FB.ui
       method: "share_open_graph"
       action_type: "og.likes"
-      action_properties: JSON.stringify(
-        object: {
-          link: $elem.prop('href'),
-          picture: $elem.data('image'),
-          name: $elem.data('title'),
-          description: $elem.data('desc')
-        }
+      object: {
+        link: $elem.prop('href'),
+        picture: $elem.data('image'),
+        name: $elem.data('title'),
+        description: $elem.data('desc')
       )
     , (response) ->


fix: test case

fix: test case

--- tests/integration/components/pricing-plan-test.coffee
+++ tests/integration/components/pricing-plan-test.coffee
@@ -13,4 +13,4 @@

   @render hbs """{{pricing-plan}}"""

-  assert.equal @$().text().trim(), 'Pay $NaN USD'
+  assert.equal @$().text().trim(), 'Pay $undefined USDEnter the number of appsPlease Enter the number of apps you would like to scan below:Pay $NaN USD'


Remove unneeded quotes

Remove unneeded quotes

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -242,7 +242,7 @@
       if folderPath
         cache.folders["#{directoryPath}#{path.sep}#{folderPath}"] = entry.dependencies
       else
-        cache.folders["#{directoryPath}"] = entry.dependencies
+        cache.folders[directoryPath] = entry.dependencies

   if directoryPath is cache.resourcePath
     for extension, paths of cacheToAdd?.extensions