Correctly render glyphs with x or y offsets

These offsets are generated by fontkit when laying out glyphs, for example accents which should appear above other glyphs. To render them correctly in PDF, we have to break out of the TJ command and move the text position manually.

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -222,7 +222,7 @@
     @addContent "BT"

     # text position
-    @addContent "#{x} #{y} Td"
+    @addContent "1 0 0 1 #{x} #{y} Tm"

     # font and font size
     @addContent "/#{@_font.id} #{@_fontSize} Tf"
@@ -244,39 +244,66 @@
       wordSpacing *= 1000 / @_fontSize

       encoded = []
-      advances = []
+      positions = []
       for word in words
-        [encodedWord, advancesWord] = @_font.encode(word, options.features)
-        unless advancesWord
-          advancesWord = (0 for i in [0...encodedWord.length])
-
+        [encodedWord, positionsWord] = @_font.encode(word, options.features)
         encoded.push encodedWord...
-        advances.push advancesWord...
-
-        # add thw word spacing to the end of the word
-        advances[advances.length - 1] += wordSpacing
+        positions.push positionsWord...
+
+        # add the word spacing to the end of the word
+        positions[positions.length - 1].xAdvance += wordSpacing
     else
-      [encoded, advances] = @_font.encode(text, options.features)
-
-    # if we have an array of advances (e.g kerning data),
-    # use the TJ operator to control spacing
-    if advances
-      commands = []
-      last = 0
-      for a, i in advances
-        # group consecutive letters together whose advance adjustments are 0
-        if a is 0 and i < advances.length - 1
-          continue
-
-        h = encoded.slice(last, i + 1).join ''
-        commands.push "<#{h}> #{-a}"
-        last = i + 1
-
-      @addContent "[#{commands.join ' '}] TJ"
-    else
-      # otherwise, just use the normal Tj operator
-      @addContent "<#{encoded.join ''}> Tj"
-
+      [encoded, positions] = @_font.encode(text, options.features)
+
+    scale = @_fontSize / 1000
+    commands = []
+    last = 0
+    hadOffset = no
+
+    # Adds a segment of text to the TJ command buffer
+    addSegment = (cur) =>
+      if last < cur
+        hex = encoded.slice(last, cur).join ''
+        advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth
+        commands.push "<#{hex}> #{-advance}"
+
+      last = cur
+
+    # Flushes the current TJ commands to the output stream
+    flush = (i) =>
+      addSegment i
+
+      if commands.length > 0
+        @addContent "[#{commands.join ' '}] TJ"
+        commands.length = 0
+
+    for pos, i in positions
+      # If we have an x or y offset, we have to break out of the current TJ command
+      # so we can move the text position.
+      if pos.xOffset or pos.yOffset
+        # Flush the current buffer
+        flush i
+
+        # Move the text position and flush just the current character
+        @addContent "1 0 0 1 #{x + pos.xOffset * scale} #{y + pos.yOffset * scale} Tm"
+        flush i + 1
+
+        hadOffset = yes
+      else
+        # If the last character had an offset, reset the text position
+        if hadOffset
+          @addContent "1 0 0 1 #{x} #{y} Tm"
+          hadOffset = no
+
+        # Group segments that don't have any advance adjustments
+        unless pos.xAdvance - pos.advanceWidth is 0
+          addSegment i + 1
+
+      x += pos.xAdvance * scale
+
+    # Flush any remaining commands
+    flush i
+
     # end the text object
     @addContent "ET"


allow setting of list bullet circle

allow setting of list bullet circle

this change will allow users to set the size of the circle on bullet list item in the options via the setting "bulletRadius"

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -70,7 +70,8 @@
   list: (list, x, y, options, wrapper) ->
     options = @_initOptions(x, y, options)

-    r = Math.round (@_font.ascender / 1000 * @_fontSize) / 3
+    midLine = Math.round (@_font.ascender / 1000 * @_fontSize) / 2
+    r = options.bulletRadius or Math.round (@_font.ascender / 1000 * @_fontSize) / 3
     indent = options.textIndent or r * 5
     itemIndent = options.bulletIndent or r * 8

@@ -102,7 +103,7 @@
         wrapper.lineWidth -= diff
         level = l

-      @circle @x - indent + r, @y + r + (r / 2), r
+      @circle @x - indent + r, @y + midLine, r
       @fill()

     wrapper.on 'sectionStart', =>


Changed instance variables to methods.

Changed instance variables to methods.

--- lib/mylist_atom.coffee
+++ lib/mylist_atom.coffee
@@ -21,18 +21,48 @@

 class MylistEntry
   constructor : (@body) ->
-    b = $(@body)
-    @title = b.find('title').text()
-    @video_id = b.find('link').attr('href').split('/')[4]
-    @timelike_id = Number b.find('id').text().split(',')[1].split(':')[1].split('/')[2]
-    @published = b.find('published').text()
-    @updated = b.find('updated').text()
+    @b = $(@body)
+    @c = @b.find('content')
+    @.get_all_info()
+    @b = @c = null

-    c = b.find('content')
-    @thumbnail_url = c.find('img').attr('src')
-    @description = c.find('.nico-description').text()
-    @length = @.convert_to_sec c.find('.nico-info-length').text()
-    @info_date = @.convert_to_unix_time c.find('.nico-info-date').text()
+  get_all_info : ->
+    @.get_title()
+    @.get_video_id()
+    @.get_timelike_id()
+    @.get_published_time()
+    @.get_updated_time()
+    @.get_thumbnail_url()
+    @.get_description()
+    @.get_length()
+    @.get_info_date()
+
+  get_title : ->
+    @title = @b.find('title').text()
+
+  get_video_id : ->
+    @video_id = @b.find('link').attr('href').split('/')[4]
+
+  get_timelike_id : ->
+    @timelike_id = Number @b.find('id').text().split(',')[1].split(':')[1].split('/')[2]
+
+  get_published_time : ->
+    @published = @b.find('published').text()
+
+  get_updated_time : ->
+    @updated = @b.find('updated').text()
+
+  get_thumbnail_url : ->
+    @thumbnail_url = @c.find('img').attr('src')
+
+  get_description : ->
+    @description = @c.find('.nico-description').text()
+
+  get_length : ->
+    @length = @.convert_to_sec @c.find('.nico-info-length').text()
+
+  get_info_date : ->
+    @info_date = @.convert_to_unix_time @c.find('.nico-info-date').text()

   convert_to_sec : (string) ->
     s = string.split(':')


fixup

fixup

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -71,11 +71,9 @@

     handle_connection: (editor) ->
         buffer = editor.getBuffer()
-        subscriptions = new CompositeDisposable
-        subscriptions.add buffer.onDidSave(@save)
-        dispose = ->
-            subscriptions.dispose()
-        subscriptions.add buffer.onDidDestroy(dispose)
+        @subscriptions = new CompositeDisposable
+        @subscriptions.add buffer.onDidSave(@save)
+        @subscriptions.add buffer.onDidDestroy(@close)

     send: (cmd) ->
         if @online
@@ -95,12 +93,14 @@
         @socket.write data
         @send ""

-    close: ->
+    close: =>
+        console.log "[ratom] closing #{path.basename @tempfile}"
         if @online
             @online = false
             @send "close"
             @send ""
             @socket.end()
+        @subscriptions.dispose()

 module.exports =


allow custom display-name

allow custom display-name

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -17,8 +17,13 @@
     set: (index, value) ->
         @settings[index] = value
         if index == "display-name"
-            @remote_address = value.split(":")[0]
-            @basename = path.basename(value.split(":")[1])
+            m = value.match /(.*?):(.*)$/
+            if m and m[2]?
+                @remote_address = value.split(":")[0]
+                @basename = path.basename(value.split(":")[1])
+            else
+                @remote_address = "unknown"
+                @basename = value
         else if index == "data"
             @datasize = parseInt(value,10)


:bug: fix

:bug: fix

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -355,7 +355,7 @@

   # Greatly depending on fold, and what range is folded is vary from languages.
   # So we need to adjust endRow based on scope.
-  getRowRangeForBufferRow: (bufferRow) ->
+  getFoldRowRangeForBufferRow: (bufferRow) ->
     for currentRow in [bufferRow..0] by -1
       [startRow, endRow] = @editor.languageMode.rowRangeForCodeFoldAtBufferRow(currentRow) ? []
       unless startRow? and (startRow <= bufferRow <= endRow) and @isIncludeFunctionScopeForRow(startRow)


cleanup

cleanup

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -50,12 +50,6 @@

   stopSelection: ->
     @canSelect = false
-
-  getUnionedRangeUnlessSelectionWasEmpty: (selection, range) ->
-    if selection.isEmpty()
-      range
-    else
-      selection.getBufferRange().union(range)

   getNormalizedHeadBufferPosition: (selection) ->
     head = selection.getHeadBufferPosition()
@@ -264,7 +258,7 @@

   getPointToSearchFrom: (selection, searchFrom) ->
     switch searchFrom
-      when 'head' then swrap(selection).getNormalizedBufferPosition()
+      when 'head' then @getNormalizedHeadBufferPosition(selection)
       when 'start' then swrap(selection).getBufferPositionFor('start')

   # Allow override @allowForwarding by 2nd argument.
@@ -781,8 +775,8 @@
     {range: found, whichIsHead: 'end'}

   getRange: (selection) ->
-    unless pattern = @globalState.get('lastSearchPattern')
-      return null
+    pattern = @globalState.get('lastSearchPattern')
+    return unless pattern?

     fromPoint = selection.getHeadBufferPosition()
     {range, whichIsHead} = @findMatch(fromPoint, pattern)
@@ -885,7 +879,6 @@
   @extend()
   member: ['AFunction', 'InnerParagraph']

-
 # FIXME: make Motion.CurrentSelection to TextObject then use concatTextObject
 class ACurrentSelectionAndAPersistentSelection extends TextObject
   @extend()


:gift: UnionTextObject and AFunctionOrInnerPair

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -1,6 +1,11 @@
 {Range, Point} = require 'atom'
 _ = require 'underscore-plus'

+# [TODO] Need overhaul
+#  - [ ] must have getRange(selection) ->
+#  - [ ] Remove selectTextObject?
+#  - [ ] Make expandable by selection.getBufferRange().union(@getRange(selection))
+#  - [ ] Count support(priority low)?
 Base = require './base'
 swrap = require './selection-wrapper'
 globalState = require './global-state'
@@ -561,7 +566,10 @@
       return row - 1
     lastRow

-  getRange: (startRow) ->
+  getRange: (selection) ->
+    @getRangeFromRow(selection.getBufferRange().start.row)
+
+  getRangeFromRow: (startRow) ->
     isBlank = @editor.isBufferRowBlank.bind(@editor)
     wasBlank = isBlank(startRow)
     fn = (row) -> isBlank(row) is wasBlank
@@ -571,12 +579,12 @@
     [startRow, endRow] = selection.getBufferRowRange()

     if firstTime and not @isMode('visual', 'linewise')
-      swrap(selection).setBufferRangeSafely @getRange(startRow)
-    else
+      swrap(selection).setBufferRangeSafely @getRange(selection)
+    else if not @instanceof('Indentation')
       point = if selection.isReversed()
-        @getRange(startRow - 1)?.start
+        @getRangeFromRow(startRow - 1)?.start
       else
-        @getRange(endRow + 1)?.end
+        @getRangeFromRow(endRow + 1)?.end
       selection.selectToBufferPosition point if point?

   selectTextObject: (selection) ->
@@ -597,8 +605,8 @@
 class Indentation extends Paragraph
   @extend(false)

-  getRange: (startRow) ->
-    return if @editor.isBufferRowBlank(startRow)
+  getRange: (selection) ->
+    startRow = selection.getBufferRange().start.row
     baseIndentLevel = getIndentLevelForBufferRow(@editor, startRow)
     fn = (row) =>
       if @editor.isBufferRowBlank(row)
@@ -650,18 +658,21 @@
   getFoldRowRangesContainsForRow: (row) ->
     getCodeFoldRowRangesContainesForRow(@editor, row, true)?.reverse()

-  selectTextObject: (selection) ->
+  getRange: (selection) ->
     range = selection.getBufferRange()
     rowRanges = @getFoldRowRangesContainsForRow(range.start.row)
-    return unless rowRanges?
+    return unless rowRanges.length

     if (rowRange = rowRanges.shift())?
       rowRange = @adjustRowRange(rowRange)
       targetRange = getBufferRangeForRowRange(@editor, rowRange)
       if targetRange.isEqual(range) and rowRanges.length
         rowRange = @adjustRowRange(rowRanges.shift())
-    if rowRange?
-      swrap(selection).selectRowRange(rowRange)
+
+    getBufferRangeForRowRange(@editor, rowRange)
+
+  selectTextObject: (selection) ->
+    swrap(selection).setBufferRangeSafely(@getRange(selection))

 class AFold extends Fold
   @extend()
@@ -813,3 +824,26 @@

 class InnerRangeMarker extends RangeMarker
   @extend()
+
+# [TODO] This class expects member have "getRange = (selection) ->" method
+class UnionTextObject extends TextObject
+  @extend(false)
+  member: []
+
+  getUnionRange: (selection) ->
+    unionRange = null
+    for member in @member when range = @new(member).getRange(selection)
+      console.log [member, range?.toString()]
+      if unionRange?
+        unionRange = unionRange.union(range)
+      else
+        unionRange = range
+    unionRange
+
+  selectTextObject: (selection) ->
+    range = @getUnionRange(selection)
+    swrap(selection).setBufferRangeSafely(range)
+
+class AFunctionOrInnerParagraph extends UnionTextObject
+  @extend()
+  member: ['AFunction', 'InnerParagraph']


sitewide: removed some files from includes

--- client/includes.coffee
+++ client/includes.coffee
@@ -505,7 +505,6 @@
   "app/Applications/Topics.kdapplication/AppView.coffee",
   "app/Applications/Topics.kdapplication/ContentDisplays/Topic.coffee",
   "app/Applications/Topics.kdapplication/ContentDisplays/TopicSplitViewController.coffee",
-  "app/Applications/Topics.kdapplication/Views/TopicsInnerNavigation.coffee",
   "app/Applications/Topics.kdapplication/Views/TopicsListItemView.coffee",

   # GROUPS
@@ -522,7 +521,6 @@
   "app/Applications/Groups.kdapplication/Views/groupadminmodal.coffee",
   "app/Applications/Groups.kdapplication/Views/groupseditablewebhookview.coffee",
   "app/Applications/Groups.kdapplication/Views/groupsformgeneratorview.coffee",
-  "app/Applications/Groups.kdapplication/Views/GroupsInnerNavigation.coffee",
   "app/Applications/Groups.kdapplication/Views/groupsinvitationlistitemview.coffee",
   "app/Applications/Groups.kdapplication/Views/groupsinvitationrequestlistitemview.coffee",
   "app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee",
@@ -552,7 +550,6 @@
   # APPS VIEWS
   "app/Applications/Apps.kdapplication/AppView.coffee",

-  "app/Applications/Apps.kdapplication/Views/AppsInnerNavigation.coffee",
   "app/Applications/Apps.kdapplication/Views/AppsListItemView.coffee",
   "app/Applications/Apps.kdapplication/Views/AppSubmission.coffee",
   "app/Applications/Apps.kdapplication/Views/appinfoview.coffee",
@@ -562,7 +559,6 @@
   "app/Applications/Apps.kdapplication/Views/appdetailsview.coffee",

   "app/Applications/Apps.kdapplication/ContentDisplays/controller.coffee",
-  "app/Applications/Apps.kdapplication/ContentDisplays/SingleAppNavigation.coffee",

   # MEMBERS VIEWS
   "app/Applications/Members.kdapplication/AppView.coffee",
@@ -643,6 +639,12 @@
   "app/Applications/Account.kdapplication/account/accSettingsSubscriptions.coffee",
   "app/Applications/Account.kdapplication/AppView.coffee",

+  # GROUP DASHBOARD
+
+  # "app/Applications/Dashboard.kdapplication/AppController.coffee",
+  # "app/Applications/Dashboard.kdapplication/AppView.coffee",
+
+
   # CONTENT DISPLAY VIEWS
   "app/MainApp/ContentDisplay/ContentDisplay.coffee",
   "app/MainApp/ContentDisplay/ContentDisplayController.coffee",
@@ -702,11 +704,9 @@
   "Framework/themes/default/kd.tooltip.styl",

   "stylus/app.styl",
-  "stylus/app.landing.styl",
   "stylus/app.bottom.styl",
   "stylus/app.about.styl",
   "stylus/app.commons.styl",
-  # "stylus/app.predefined.styl",
   "stylus/app.editor.styl",
   "stylus/app.finder.styl",
   "stylus/app.aceeditor.styl",
@@ -715,7 +715,6 @@
   "stylus/app.chat.styl",
   "stylus/app.settings.styl",
   "stylus/app.inbox.styl",
-  # "stylus/app.envsettings.styl",
   "stylus/app.members.styl",
   "stylus/app.comments.styl",
   "stylus/app.bootstrap.styl",
@@ -732,11 +731,14 @@
   "stylus/app.codeshare.styl",
   "stylus/app.group.general.styl",
   "stylus/app.group.dashboard.styl",
-  "stylus/app.group.landing.styl",
   "stylus/app.group.summary.styl",
   "stylus/app.user.styl",
   "stylus/app.markdown.styl",
   "stylus/temp.styl",
+  # "stylus/app.landing.styl",
+  # "stylus/app.predefined.styl",
+  # "stylus/app.envsettings.styl",
+  # "stylus/app.group.landing.styl",

   # mediaqueries should stay at the bottom
   "stylus/app.1200.styl",


client: minor: lint fix

client: minor: lint fix

--- client/stack-editor/lib/routehandler.coffee
+++ client/stack-editor/lib/routehandler.coffee
@@ -6,7 +6,7 @@
 module.exports = -> lazyrouter.bind 'stackeditor', (type, info, state, path, ctx) ->

   unless canCreateStacks()
-    new kd.NotificationView title: 'You are not allowed to create stacks!'
+    new kd.NotificationView { title: 'You are not allowed to create stacks!' }
     return kd.singletons.router.back()

   kd.singletons.appManager.open 'Stackeditor', (app) ->


Tests: Teams: Added seeTemplatePreview test.

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -597,6 +597,8 @@
     stackTabSelector     = '.team-stack-templates .kdtabhandle.stack-template.active'
     finalizeStepsButton  = "#{sidebarSelector} a[href='/Stacks/Welcome']:not(.SidebarSection-headerTitle)"

+    @seeTemplatePreview(browser)
+
     browser
       .waitForElementVisible     saveAndTestButton, 20000
       .scrollToElement           saveAndTestButton
@@ -687,6 +689,27 @@
       browser.pause 10000 # wait before credential destroy
       @createStack(browser, yes)
       @createCredential(browser, no, yes)
+
+
+  seeTemplatePreview: (browser) ->
+
+    templatePreviewButton     = '.stack-template .template-preview-button'
+    stackTemplatePreviewModal = '.stack-template-preview'
+    tabsSelector              = "#{stackTemplatePreviewModal} .kdmodal-content .kdtabhandle-tabs"
+    yamlTabSelector           = "#{tabsSelector} .yaml"
+    jsonTabSelector           = "#{tabsSelector} .json"
+    stackTabSelector          = '.team-stack-templates .kdtabhandle.stack-template'
+
+    browser
+      .waitForElementVisible  stackTabSelector, 20000
+      .click                  stackTabSelector
+      .waitForElementVisible  templatePreviewButton, 20000
+      .click                  templatePreviewButton
+      .waitForElementVisible  stackTemplatePreviewModal, 20000
+      .waitForElementVisible  yamlTabSelector, 20000
+      .waitForElementVisible  jsonTabSelector, 20000
+      .waitForElementVisible  stackModalCloseButton, 20000
+      .click                  stackModalCloseButton

   getAwsKey: -> return awsKey


fix typo in extend_time controller

fix typo in extend_time controller

Change-Id: I2d0ab394b80a4b098339b45a8ae3afe59d575e68
Reviewed-on: https://gerrit.instructure.com/37911
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
QA-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/ember/quizzes/controllers/quiz/moderate/extend_time_controller.coffee
+++ app/coffeescripts/ember/quizzes/controllers/quiz/moderate/extend_time_controller.coffee
@@ -12,7 +12,7 @@
     title: I18n.t('extend_time', 'Extend Time')

     extendFor: ( ->
-      I18n.t('extensions_student', 'Extend time for for %{student}', student: @get("name"))
+      I18n.t('extensions_student', 'Extend time for %{student}', student: @get("name"))
     ).property('name')

     timeOptions: [


Shared VMs are optional, and definitely not free.

--- client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -141,17 +141,17 @@
       @addSubView @hostSelector = new KDFormViewWithFields
         cssClass         : "host-selector"
         fields           :
-          label          :
-            itemClass    : KDCustomHTMLView
-            tagName      : 'h2'
-            cssClass     : 'heading'
-            partial      : "2 Do you want a shared host for your Group?"
-          sharedHost     :
-            itemClass    : KDOnOffSwitch
-            name         : "shared-vm"
-            defaultValue : no
-            cssClass     : "shared-vm"
-            callback     : @bound "hostChanged"
+          # label          :
+          #   itemClass    : KDCustomHTMLView
+          #   tagName      : 'h2'
+          #   cssClass     : 'heading'
+          #   partial      : "2 Do you want a shared host for your Group?"
+          # sharedHost     :
+          #   itemClass    : KDOnOffSwitch
+          #   name         : "shared-vm"
+          #   defaultValue : no
+          #   cssClass     : "shared-vm"
+          #   callback     : @bound "hostChanged"
           # selector    :
           #   name      : "host"
           #   itemClass : HostCreationSelector
@@ -173,12 +173,12 @@
             itemClass     : KDCustomHTMLView
             tagName       : 'h2'
             cssClass      : 'heading'
-            partial       : "3 How much resources do you want to allocate to your each user?"
+            partial       : "2 How much resources do you want to allocate to your each user?"
           sharedHost      :
             itemClass     : KDSelectBox
             type          : "select"
             name          : "allocation"
-            defaultValue  : "10"
+            defaultValue  : "0"
             cssClass      : "allocation"
             change        : @bound "allocationChanged"
             selectOptions : [
@@ -214,6 +214,7 @@
             defaultValue : yes
             cssClass     : "right-aligned"

+      @allocationChanged()
       @emit 'ready'

   hostChanged:->


Don't wrap in cmd.exe

Don't wrap in cmd.exe

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -9,15 +9,7 @@
 # Spawn the Update.exe with the given arguments and invoke the callback when
 # the command completes.
 exports.spawn = (args, callback) ->
-  command = process.env.comspec or 'cmd.exe'
-  args = args.map (arg) -> "\"#{arg.toString().replace(/"/g, '\\"')}\""
-  if /\s/.test(updateDotExe)
-    args.unshift("\"#{updateDotExe}\"")
-  else
-    args.unshift(updateDotExe)
-  args = ['/s', '/c', "\"#{args.join(' ')}\""]
-
-  updateProcess = ChildProcess.spawn(command, args, windowsVerbatimArguments: true)
+  updateProcess = ChildProcess.spawn(updateDotExe, args)

   stdout = ''
   updateProcess.stdout.on 'data', (data) -> stdout += data


Disable animations and delayIn/Out timeouts by default for KDToolTip

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -700,10 +700,10 @@
     o.offset    or=
       top         : 0
       left        : 0
-    o.delayIn   or= 300
-    o.delayOut  or= 300
+    o.delayIn   or= 0
+    o.delayOut  or= 0
     o.html      or= yes
-    o.animate   or= yes
+    o.animate   or= no
     o.selector  or= null
     o.gravity   or= placementMap[o.placement]
     o.fade      or= o.animate


Set fallback to make tipsy work.

Set fallback to make tipsy work.

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -739,6 +739,7 @@
     o.engine    or= "tipsy" # we still can use twipsy
     o.gravity   or= placementMap[o.placement]
     o.fade      or= o.animate
+    o.fallback  or= o.title

     @listenTo
       KDEventTypes        : "viewAppended"


Improve color equality

Improve color equality


Add option to disable border element

Add option to disable border element


Only returning empty name if name is undefined, not just falsy

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -250,7 +250,7 @@
 		default: ""
 		get: ->
 			name = @_getPropertyValue("name")
-			return name or ""
+			return name ? ""

 		set: (value) ->
 			@_setPropertyValue("name", value)


Chrome point fix

Chrome point fix

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -1004,6 +1004,7 @@
 	ancestors.push(layer) if includeLayer

 	for ancestor in ancestors
+		continue unless ancestor.matrix3d
 		point = ancestor.matrix3d.inverse().point(point)

 	return point


introduction-tooltip: Prevented showing previously showed tooltip.

--- client/landing/app/CommonViews/introductiontooltip/introductiontooltipcontroller.coffee
+++ client/landing/app/CommonViews/introductiontooltip/introductiontooltipcontroller.coffee
@@ -4,10 +4,11 @@

     super options, data

-    @currentTimestamp = Date.now()
-    @shouldAddOverlay = no
-    @visibleTooltips  = []
-    @stepByStepGroups = {}
+    @currentTimestamp  = Date.now()
+    @shouldAddOverlay  = no
+    @visibleTooltips   = []
+    @displayedTooltips = []
+    @stepByStepGroups  = {}

     @getSingleton("mainController").on "FrameworkIsReady", =>
       @init()
@@ -47,6 +48,9 @@

     return if not assets or @isExpired data.expiryDate
     return if data.visibility is "stepByStep" and data.index > @stepByStepGroups[data.groupName].currentIndex
+    return if @displayedTooltips.indexOf(data.introId) > -1
+
+    @displayedTooltips.push data.introId

     {parentView, tooltipView} = assets
     tooltip = new IntroductionTooltip {


Don't color unquoted https://... as comment

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -902,7 +902,7 @@
         'captures':
           '0':
             'name': 'punctuation.definition.comment.stata'
-        'begin': '(//)'
+        'begin': '((?<!:)//)'
         'end': '(?=\\n)'
         'name': 'comment.line.double-slash.stata'
         'patterns': [


Add test for options

Add test for options

--- test/tests/LayerStatesTest.coffee
+++ test/tests/LayerStatesTest.coffee
@@ -55,6 +55,7 @@
 			animation.options.curve.should.equal "spring(1, 2, 3)"

 			Framer.resetDefaults()
+

 	describe "Switch", ->

@@ -293,3 +294,12 @@
 					state: y: 10
 					previous: x: 300
 			expect(throwing).to.throw(/You can't override special state 'previous'/)
+
+	describe "Options", ->
+		it "should listen to layer.options", ->
+			layer = new Layer
+			layer.options =
+				time: 4
+			animation = layer.animateTo
+				x: 100
+			animation.options.time.should.equal 4


updated grunt-jaded

updated grunt-jaded

--- build/grunt.coffee
+++ build/grunt.coffee
@@ -94,20 +94,19 @@
     grunt.log.writeln "starting servers..."
     require "#{app.paths.server}/server"

-  ## jaded
+## jaded
   grunt.registerTask "jaded", "compile jaded templates", ->
     jaded = require 'jaded'
-    {baseName}  = require 'path'
+    {basename, extname}  = require 'path'
     src   = app.paths.client + '/templates'
     dest  = app.paths.public + '/templates'
-    grunt.file.recurse src,
+    grunt.file.recurse src,
       (absolute, root, subdir, filename) ->
-        [name, _] = filename.split '.'
-        dest = dest + "/" + "#{name}.js"
-        console.log dest
+        name = basename filename, extname filename
         template = jaded.compile grunt.file.read(absolute),
           development: true
-          rivets:      true
-          amd:         true
-        grunt.file.write dest, template
+          rivets: true
+          amd: true
+          filename: absolute
+        grunt.file.write "#{dest}/#{name}.js", template


PMs: Better positioning of popups.

PMs: Better positioning of popups.

--- client/app/js/views/private_messages_view.coffee
+++ client/app/js/views/private_messages_view.coffee
@@ -55,11 +55,14 @@
     username = message.id
     $popup = @$findPopup(username)
     $popup.addClass('hidden')
+    @repositionPopups()

   showPopup: (message) =>
     username = message.id
     $popup = @$findPopup(username)
+    @$el.append($popup)
     $popup.removeClass('hidden')
+    @repositionPopups()

   # todo: make this and receiveMessage construct messages from a common source
   addLogMessages: ($popup, log) =>
@@ -148,7 +151,7 @@
     $popup.css(left: leftOffset + index * $popup.outerWidth(true))

   repositionPopups: =>
-    @$(".popup").each (index, self) =>
+    @$(".popup:visible").each (index, self) =>
       @positionPopup($(self), index)

   $findPopup: (id) =>


removed unnecessary tags

removed unnecessary tags

--- client/app/lib/providers/snapshotlistitem.coffee
+++ client/app/lib/providers/snapshotlistitem.coffee
@@ -99,11 +99,7 @@
       pistachio       : """

           {{> labelView}}
-
-            [size: #{data.storageSize}GB,
-            created:
-              #{SnapshotListItem.prettyCreatedAt data.createdAt}]
-
+          size: #{data.storageSize}GB, created: #{SnapshotListItem.prettyCreatedAt data.createdAt}

             {{> infoRenameBtn}}
             {{> infoDeleteBtn}}


Fixing handler scope for `error with line number` test and encoding error buffer to string.

--- spec/error_with_line_number.coffee
+++ spec/error_with_line_number.coffee
@@ -25,8 +25,8 @@
 				unless report_msg?
 					report_msg = data.toString().stripColors.replace /\n/g, ""

-			toaster.stderr.on 'data', ( error )->
-				@callback error
+			toaster.stderr.on 'data', ( error )=>
+				@callback error.toString()

 			toaster.on 'exit', (data)=>
 				@callback null, report_msg


Add test case for #6

Add test case for #6

--- spec/jquery.turbolinks_spec.coffee
+++ spec/jquery.turbolinks_spec.coffee
@@ -70,9 +70,15 @@
       $.setReadyEvent('page:load')
       $.isReady = true

+    it 'should call trigger right after add to waiting list', ->
+      $(callback = sinon.spy())
+      callback.should.have.been.calledOnce
+
+    it 'should not call trigger after page:fetch and before page:load', ->
+      $(document).trigger('page:fetch')
       $(callback1 = sinon.spy())
+      callback1.should.have.not.been.called
+
+      $(document).trigger('page:load')
       $(callback2 = sinon.spy())
-
-    it 'should call trigger right after add to waiting list', ->
-      callback1.should.have.been.calledOnce
       callback2.should.have.been.calledOnce


Increase spec output.

Increase spec output.

--- spec/javascripts/spec_helper.coffee
+++ spec/javascripts/spec_helper.coffee
@@ -41,4 +41,4 @@
 #= require application

 window.expect = chai.expect
-chai.config.truncateThreshold = 100
+chai.config.truncateThreshold = 150


More flexible menu specs

More flexible menu specs

--- spec/utils/menu-manager-spec.coffee
+++ spec/utils/menu-manager-spec.coffee
@@ -25,14 +25,15 @@
       ideMenu = MenuManager.getMenu()

       expect(ideMenu.submenu.length).toBe(3)
+      idx = 0

-      expect(ideMenu.submenu[0].label).toBe('Log in to Spark Cloud...')
-      expect(ideMenu.submenu[0].command).toBe('spark-dev:login')
+      expect(ideMenu.submenu[idx].label).toBe('Log in to Spark Cloud...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:login')

-      expect(ideMenu.submenu[1].type).toBe('separator')
+      expect(ideMenu.submenu[idx++].type).toBe('separator')

-      expect(ideMenu.submenu[2].label).toBe('Show serial monitor')
-      expect(ideMenu.submenu[2].command).toBe('spark-dev:show-serial-monitor')
+      expect(ideMenu.submenu[idx].label).toBe('Show serial monitor')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:show-serial-monitor')

   it 'checks menu for logged in user', ->
@@ -48,35 +49,36 @@
       ideMenu = MenuManager.getMenu()

       expect(ideMenu.submenu.length).toBe(11)
+      idx = 0

-      expect(ideMenu.submenu[0].label).toBe('Log out foo@bar.baz')
-      expect(ideMenu.submenu[0].command).toBe('spark-dev:logout')
+      expect(ideMenu.submenu[idx].label).toBe('Log out foo@bar.baz')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:logout')

-      expect(ideMenu.submenu[1].type).toBe('separator')
+      expect(ideMenu.submenu[idx++].type).toBe('separator')

-      expect(ideMenu.submenu[2].label).toBe('Select device...')
-      expect(ideMenu.submenu[2].command).toBe('spark-dev:select-device')
+      expect(ideMenu.submenu[idx].label).toBe('Select device...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:select-device')

-      expect(ideMenu.submenu[3].type).toBe('separator')
+      expect(ideMenu.submenu[idx++].type).toBe('separator')

-      expect(ideMenu.submenu[4].label).toBe('Claim device...')
-      expect(ideMenu.submenu[4].command).toBe('spark-dev:claim-device')
+      expect(ideMenu.submenu[idx].label).toBe('Claim device...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:claim-device')

-      expect(ideMenu.submenu[5].label).toBe('Identify device...')
-      expect(ideMenu.submenu[5].command).toBe('spark-dev:identify-device')
+      expect(ideMenu.submenu[idx].label).toBe('Identify device...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:identify-device')

-      expect(ideMenu.submenu[6].label).toBe('Setup device\'s WiFi...')
-      expect(ideMenu.submenu[6].command).toBe('spark-dev:setup-wifi')
+      expect(ideMenu.submenu[idx].label).toBe('Setup device\'s WiFi...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:setup-wifi')

-      expect(ideMenu.submenu[7].type).toBe('separator')
+      expect(ideMenu.submenu[idx++].type).toBe('separator')

-      expect(ideMenu.submenu[8].label).toBe('Compile in the cloud')
-      expect(ideMenu.submenu[8].command).toBe('spark-dev:compile-cloud')
+      expect(ideMenu.submenu[idx].label).toBe('Compile in the cloud')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:compile-cloud')

-      expect(ideMenu.submenu[9].type).toBe('separator')
+      expect(ideMenu.submenu[idx++].type).toBe('separator')

-      expect(ideMenu.submenu[10].label).toBe('Show serial monitor')
-      expect(ideMenu.submenu[10].command).toBe('spark-dev:show-serial-monitor')
+      expect(ideMenu.submenu[idx].label).toBe('Show serial monitor')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:show-serial-monitor')

       SettingsHelper.clearCredentials()

@@ -96,18 +98,19 @@
       ideMenu = MenuManager.getMenu()

       expect(ideMenu.submenu.length).toBe(15)
+      idx = 3

-      expect(ideMenu.submenu[3].label).toBe('Rename Foo...')
-      expect(ideMenu.submenu[3].command).toBe('spark-dev:rename-device')
+      expect(ideMenu.submenu[idx].label).toBe('Rename Foo...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:rename-device')

-      expect(ideMenu.submenu[4].label).toBe('Remove Foo...')
-      expect(ideMenu.submenu[4].command).toBe('spark-dev:remove-device')
+      expect(ideMenu.submenu[idx].label).toBe('Remove Foo...')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:remove-device')

-      expect(ideMenu.submenu[5].label).toBe('Show cloud variables and functions')
-      expect(ideMenu.submenu[5].command).toBe('spark-dev:show-cloud-variables-and-functions')
+      expect(ideMenu.submenu[idx].label).toBe('Show cloud variables and functions')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:show-cloud-variables-and-functions')

-      expect(ideMenu.submenu[6].label).toBe('Flash Foo via the cloud')
-      expect(ideMenu.submenu[6].command).toBe('spark-dev:flash-cloud')
+      expect(ideMenu.submenu[idx].label).toBe('Flash Foo via the cloud')
+      expect(ideMenu.submenu[idx++].command).toBe('spark-dev:flash-cloud')

       SettingsHelper.clearCurrentCore()
       SettingsHelper.clearCredentials()


Implement unsubscriptions.

Implement unsubscriptions.

--- scripts/mailchimp-subscribe.coffee
+++ scripts/mailchimp-subscribe.coffee
@@ -10,21 +10,25 @@
 #
 # Commands:
 #   hubot subscribe <email> - Add email to list
+#   hubot unsubscribe <email> - Remove email from list
 #
 # Author:
-#   max, lmarburger, m-baumgartner
+#   max, lmarburger, m-baumgartner, sporkmonger

 MailChimpAPI = require('mailchimp').MailChimpAPI

 apiKey = process.env.MAILCHIMP_API_KEY
 listId = process.env.MAILCHIMP_LIST_ID

-module.exports = (robot)->
-    robot.respond /subscribe (.+@.+)/i, (message)->
-      subscribeToList message
+module.exports = (robot) ->
+  robot.respond /\bsubscribe (.+@.+)/i, (message) ->
+    subscribeToList message
+  robot.respond /\bunsubscribe (.+@.+)/i, (message) ->
+    unsubscribeFromList message

 subscribeToList = (message) ->
   emailAddress = message.match[1]
+  message.reply "Attempting to subscribe #{emailAddress}..."

   try
     api = new MailChimpAPI(apiKey,
@@ -43,4 +47,27 @@
     if error
       message.send "Uh oh, something went wrong: #{error.message}"
     else
-      message.send "You succesfully subscribed #{emailAddress}."
+      message.send "You successfully subscribed #{emailAddress}."
+
+unsubscribeFromList = (message) ->
+  emailAddress = message.match[1]
+  message.reply "Attempting to unsubscribe #{emailAddress}..."
+
+  try
+    api = new MailChimpAPI(apiKey,
+      version: "1.3"
+      secure: false
+    )
+  catch error
+    console.log error.message
+    return
+
+  api.listUnsubscribe
+    id:            listId
+    email_address: emailAddress
+    double_optin:  false
+  , (error, data) ->
+    if error
+      message.send "Uh oh, something went wrong: #{error.message}"
+    else
+      message.send "You successfully unsubscribed #{emailAddress}."


fix(squirrel): remove apm from path

fix(squirrel): remove apm from path

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -95,7 +95,7 @@
     else
       callback(new Error('Registry query for PATH failed'))

-# Add atom and apm to the PATH
+# Add N1 to the PATH
 #
 # This is done by adding .cmd shims to the root bin folder in the N1
 # install directory that point to the newly installed versions inside
@@ -110,19 +110,9 @@
     relativeN1ShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'N1.sh'))
     nylasShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeN1ShPath.replace(/\\/g, '/')}\" \"$@\""

-    apmCommandPath = path.join(binFolder, 'apm.cmd')
-    relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
-    apmCommand = "@echo off\r\n\"%~dp0\\#{relativeApmPath}\" %*"
-
-    apmShCommandPath = path.join(binFolder, 'apm')
-    relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
-    apmShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeApmShPath.replace(/\\/g, '/')}\" \"$@\""
-
     fs.writeFile nylasCommandPath, nylasCommand, ->
       fs.writeFile nylasShCommandPath, nylasShCommand, ->
-        fs.writeFile apmCommandPath, apmCommand, ->
-          fs.writeFile apmShCommandPath, apmShCommand, ->
-            callback()
+        callback()

   addBinToPath = (pathSegments, callback) ->
     pathSegments.push(binFolder)
@@ -141,7 +131,7 @@
       else
         callback()

-# Remove N1 and apm from the PATH
+# Remove N1 from the PATH
 removeCommandsFromPath = (callback) ->
   getPath (error, pathEnv) ->
     return callback(error) if error?


update keymap example

update keymap example

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -143,7 +143,7 @@
   # 'g _': 'vim-mode-plus:snake-case'
   # 'g r':   'vim-mode-plus:reverse'
   # 'ctrl-s': 'vim-mode-plus:transform-string-by-select-list'
-  # 'g m': 'vim-mode-plus:create-range-marker'
+  # 'g m': 'vim-mode-plus:toggle-range-marker'

   'ctrl-a': 'vim-mode-plus:increase'
   'ctrl-x': 'vim-mode-plus:decrease'


Fix a typo in last commit

Fix a typo in last commit

--- src/audio/sound.directional.coffee
+++ src/audio/sound.directional.coffee
@@ -3,9 +3,9 @@
         super name, mesh, options

     _init: (options) ->
-        @panner.coneInnerAngle = angles.innerAngleDegrees
-        @panner.coneOuterAngle = angles.outerAngleDegrees
-        @panner.coneOuterGain = angles.outerGainFactor
+        @panner.coneInnerAngle = @angles.innerAngleDegrees
+        @panner.coneOuterAngle = @angles.outerAngleDegrees
+        @panner.coneOuterGain = @angles.outerGainFactor
         return

     update: (dt) ->


refraish page after website creation to show it in the fiexed top nav list

--- BackofficeBundle/Resources/public/coffee/extendView/submitAdmin.coffee
+++ BackofficeBundle/Resources/public/coffee/extendView/submitAdmin.coffee
@@ -31,6 +31,7 @@
                   html: response
                   domContainer: $('h1.page-title').parent()
                 ))
+                location.reload 1 if viewContext.options.entityType == 'websites'
                 $(document).scrollTop 0
               200: (response) ->
                 widgetChannel.trigger 'form-error', viewContext


Add identifier and versions fields

Add identifier and versions fields

--- server/app/bongo/models/app.coffee
+++ server/app/bongo/models/app.coffee
@@ -41,6 +41,10 @@
       ]

     schema          :
+      identifier    :
+        type        : String
+        set         : (value)-> value.trim()
+        required    : yes
       title         :
         type        : String
         set         : (value)-> value.trim()
@@ -56,6 +60,7 @@
           default   : 0
       thumbnails    : [Object]
       screenshots   : [Object]
+      versions      : [String]
       meta          : require "bongo/bundles/meta"
       manifest      : Object
       type          :


server port test

server port test

--- server/config/server-config.coffee
+++ server/config/server-config.coffee
@@ -1,4 +1,4 @@
 module.exports =
-	port: 3000,
+	port: 4000,
 	secret: 'test',
 	cookieSecret: 'test'


Delete __v property when comparing submits

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -70,6 +70,7 @@
         oldSubmit = (await Submit.findById(newSubmit._id))?.toObject()

         if oldSubmit
+            delete oldSubmit.__v
             newSubmit.source = oldSubmit.source
             newSubmit.results = oldSubmit.results
             newSubmit.comments = oldSubmit.comments


checking date value

checking date value

--- server/lib/konnector_poller.coffee
+++ server/lib/konnector_poller.coffee
@@ -94,7 +94,7 @@

                     diff = 0
                     # if date is present in fieldValues
-                    if konnector.fieldValues.date?
+                    if konnector.fieldValues.date? and konnector.fieldValues.date is not ''
                         now = moment()
                         firstImportDate = moment(konnector.fieldValues.date, "DD-MM-YYYY")


Trap and dump state on SIGINT.

Trap and dump state on SIGINT.

--- console/runner.coffee
+++ console/runner.coffee
@@ -171,5 +171,10 @@
       # default done_cb is a no-op
       ->

+  process.on 'SIGINT', ->
+    console.error 'Doppio caught SIGINT'
+    rs.dump_state() if jvm.dump_state
+    process.exit 0
+
   # finally set up. run it.
   run(done_cb)


fixed typo

fixed typo

--- js-library/app/js/models/fact.coffee
+++ js-library/app/js/models/fact.coffee
@@ -45,7 +45,7 @@
       click:      => @openFactlinkModal()

     $(@elements)
-      .on('mouseenter', (e)=> @highlight_and_open_balloon(e))
+      .on('mouseenter', (e)=> @highlightAndOpenBalloon(e))
       .on('mouseleave', => @considerStoppingWithHighlighting())
       .on('click', => @openFactlinkModal())
       .on 'inview', (event, isInView, visiblePart) =>


WIP Fix undo issue

WIP Fix undo issue

The problem is the way we are batching our title-editing undoable
actions. When we edit a title, and then drag a new node down or
create a new link, we don't correctly end the batch, so the new
node dragging (and possibly more new nodes and new links) all end
up in the same batch.

Undoing this batch not only undoes more than the user expected, but also
generally throws errors that can hang the application.

This commit adds more handlers that help us know when the user has
stopped editing the node.

(A better solution might be to use the undoable command "reducing"
solution created for CODAP for this exact same situation, but we still
need the endNodeEdit calls to properly update the UI state.)

[#113103807]

--- src/code/stores/graph-store.coffee
+++ src/code/stores/graph-store.coffee
@@ -100,6 +100,7 @@
     @addLink(link)

   addLink: (link) ->
+    @endNodeEdit()
     @undoRedoManager.createAndExecuteCommand 'addLink',
       execute: => @_addLink link
       undo: => @_removeLink link
@@ -113,6 +114,7 @@

   removeLink: (link) ->
+    @endNodeEdit()
     @undoRedoManager.createAndExecuteCommand 'removeLink',
       execute: => @_removeLink link
       undo: => @_addLink link
@@ -125,11 +127,13 @@

   addNode: (node) ->
+    @endNodeEdit()
     @undoRedoManager.createAndExecuteCommand 'addNode',
       execute: => @_addNode node
       undo: => @_removeNode node

   removeNode: (nodeKey) ->
+    @endNodeEdit()
     node = @nodeKeys[nodeKey]

     # create a copy of the list of links
@@ -153,6 +157,7 @@
     @updateListeners()

   moveNodeCompleted: (nodeKey, pos, originalPos) ->
+    @endNodeEdit()
     node = @nodeKeys[nodeKey]
     return unless node
     @undoRedoManager.createAndExecuteCommand 'moveNode',
@@ -176,6 +181,7 @@
     @selectionManager.selectForTitleEditing(@nodeKeys[nodeKey])

   selectNode: (nodeKey) ->
+    @endNodeEdit()
     @selectionManager.selectForInspection(@nodeKeys[nodeKey])


throw out identity operations in the UndoRedo object

--- src/coffee/client/undo_redo.coffee
+++ src/coffee/client/undo_redo.coffee
@@ -38,6 +38,10 @@
     state = initialState

     @applyOp = (op, undoable) ->
+        # Don't push useless identity operations
+        if Ot.isIdentity(op)
+            return
+
         # If we do an 'undoable' op, we lose the ability to 'redo' any previously
         # undone ops.
         if undoable


payment: refactor GroupProductSettingsView

make it easy to reuse the product creation form

--- client/landing/app/Applications/Dashboard.kdapplication/views/groupproductsettingsview.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/groupproductsettingsview.coffee
@@ -3,36 +3,53 @@
   constructor: (options = {}, data) ->
     super options, data

-    group = @getData()
+    @setClass 'group-product-section'

-    @setClass "payment-settings-view"
+    @productsView = new GroupProductsView

-    @productCreateForm = new GroupProductCreateForm
+    @forwardEvent @productsView, 'DeleteRequested', 'Product'

-    @forwardEvent @productCreateForm, 'ProductCreateRequested'
+    @productsView.on 'CreateRequested', =>
+      @showCreateModal
+        productType       : 'product'
+        isOverageEnabled  : yes

-    @reloadButton = new KDButtonView
-      cssClass   : "product-button"
-      title      : "Reload"
-      callback   : =>
-        @emit 'ProductReloadRequested'
+    @plansView = new GroupPlansView

-    @productListController = new GroupProductListController
-      group     : group
-      itemClass : GroupProductListItem
-    # @controller.loadItems()
+    @plansView.on 'CreateRequested', =>
+      @showCreateModal
+        productType         : 'plan'
+        isRecurringOptional : no
+        isOverageEnabled    : no
+        placeholders        :
+          title             : 'e.g. "Gold Plan"'
+          description       : 'e.g. "2 VMs, and a tee shirt"'

-    @list = @productListController.getListView()
-    @list.on "DeleteItem", (code)=>
-      group.deleteProduct {code}, =>
-        @emit 'ProductReloadRequested'
+  showCreateModal: (options) ->
+
+    productType = options.productType ? 'product'
+
+    modal = new KDModalView
+      overlay       : yes
+      title         : "Create #{ productType }"
+
+    createForm = new GroupProductCreateForm options
+
+    modal.addSubView createForm
+
+    createForm.on 'CancelRequested', ->
+      modal.destroy()
+
+    createForm.on 'CreateRequested', (productData) =>
+      createEventName = "#{ productType.capitalize() }CreateRequested"
+      @emit createEventName, productData
+      modal.destroy()

   setProducts: (products) ->
-    @productListController.instantiateListItems products
+    @productsView.setProducts products

   pistachio: ->
     """
-    {{> @productCreateForm}}
-    {{> @reloadButton}}
-    {{> @productListController.getView()}}
+    {{> @productsView}}
+    {{> @plansView}}
     """


Specs

Specs

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -1750,6 +1750,34 @@
               expect(fs.existsSync(filePath)).toBeTruthy()
               expect(callback).not.toHaveBeenCalled()

+          describe 'when the file is currently open', ->
+            beforeEach ->
+              waitForWorkspaceOpenEvent ->
+                atom.workspace.open(filePath)
+
+            it 'has its path updated', ->
+              fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+              atom.commands.dispatch(treeView.element, "tree-view:paste")
+
+              newPath = path.join(dirPath2, path.basename(filePath))
+              expect(atom.workspace.getActiveTextEditor().getPath()).toBe newPath
+
+            it 'does not update paths for similarly-named editors', ->
+              filePath2 = path.join(dirPath, 'test-file.txt2')
+              fs.writeFileSync(filePath2, 'copy')
+
+              waitForWorkspaceOpenEvent ->
+                atom.workspace.open(filePath2)
+
+              runs ->
+                fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+                atom.commands.dispatch(treeView.element, "tree-view:paste")
+
+                newPath = path.join(dirPath2, path.basename(filePath))
+                editors = atom.workspace.getTextEditors()
+                expect(editors[0].getPath()).toBe newPath
+                expect(editors[1].getPath()).toBe filePath2
+
         describe "when a directory is selected", ->
           it "creates a copy of the original file in the selected directory and removes the original", ->
             LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath])
@@ -2146,9 +2174,6 @@
             atom.commands.dispatch(treeView.element, "tree-view:move")
             moveDialog = atom.workspace.getModalPanels()[0].getItem()

-        afterEach ->
-          waits 50 # The move specs cause too many false positives because of their async nature, so wait a little bit before we cleanup
-
         it "opens a move dialog with the file's current path (excluding extension) populated", ->
           extension = path.extname(filePath)
           fileNameWithoutExtension = path.basename(filePath, extension)
@@ -2198,17 +2223,46 @@

           describe "when a file or directory already exists at the target path", ->
             it "shows an error message and does not close the dialog", ->
+              fs.writeFileSync(path.join(rootDirPath, 'target.txt'), '')
+              newPath = path.join(rootDirPath, 'target.txt')
+              moveDialog.miniEditor.setText(newPath)
+
+              atom.commands.dispatch moveDialog.element, 'core:confirm'
+
+              expect(moveDialog.errorMessage.textContent).toContain 'already exists'
+              expect(moveDialog.element).toHaveClass('error')
+              expect(moveDialog.element.parentElement).toBeTruthy()
+              expect(callback).not.toHaveBeenCalled()
+
+          describe 'when the file is currently open', ->
+            beforeEach ->
+              waitForWorkspaceOpenEvent ->
+                atom.workspace.open(filePath)
+
+            it 'has its path updated', ->
+              newPath = path.join(rootDirPath, 'renamed-test-file.txt')
+              moveDialog.miniEditor.setText(path.basename(newPath))
+
+              atom.commands.dispatch moveDialog.element, 'core:confirm'
+
+              expect(atom.workspace.getActiveTextEditor().getPath()).toBe newPath
+
+            it 'does not update paths for similarly-named editors', ->
+              filePath2 = path.join(dirPath, 'test-file.txt2')
+              fs.writeFileSync(filePath2, 'copy')
+
+              waitForWorkspaceOpenEvent ->
+                atom.workspace.open(filePath2)
+
               runs ->
-                fs.writeFileSync(path.join(rootDirPath, 'target.txt'), '')
-                newPath = path.join(rootDirPath, 'target.txt')
-                moveDialog.miniEditor.setText(newPath)
+                newPath = path.join(rootDirPath, 'renamed-test-file.txt')
+                moveDialog.miniEditor.setText(path.basename(newPath))

                 atom.commands.dispatch moveDialog.element, 'core:confirm'

-                expect(moveDialog.errorMessage.textContent).toContain 'already exists'
-                expect(moveDialog.element).toHaveClass('error')
-                expect(moveDialog.element.parentElement).toBeTruthy()
-                expect(callback).not.toHaveBeenCalled()
+                editors = atom.workspace.getTextEditors()
+                expect(editors[0].getPath()).toBe newPath
+                expect(editors[1].getPath()).toBe filePath2

         describe "when 'core:cancel' is triggered on the move dialog", ->
           it "removes the dialog and focuses the tree view", ->
@@ -2281,9 +2335,6 @@
               atom.commands.dispatch(treeView.element, "tree-view:move")
               moveDialog = atom.workspace.getModalPanels()[0].getItem()

-        afterEach ->
-          waits 50 # The move specs cause too many false positives because of their async nature, so wait a little bit before we cleanup
-
         it "opens a move dialog with the folder's current path populated", ->
           extension = path.extname(dirPath)
           expect(moveDialog.element).toExist()
@@ -2302,6 +2353,20 @@
             atom.commands.dispatch moveDialog.element, 'core:confirm'
             expect(atom.workspace.getActivePaneItem()).toBe(editor)
             expect(editor.getPath()).toBe(filePath.replace('test-dir', 'renamed-dir'))
+
+          it 'does not update paths for editors with similar paths', ->
+            waitForWorkspaceOpenEvent ->
+              atom.workspace.open(filePath2)
+
+            runs ->
+              newPath = path.join(rootDirPath, 'renamed-dir')
+              moveDialog.miniEditor.setText(newPath)
+
+              atom.commands.dispatch moveDialog.element, 'core:confirm'
+
+              editors = atom.workspace.getTextEditors()
+              expect(editors[0].getPath()).toBe filePath.replace('test-dir', 'renamed-dir')
+              expect(editors[1].getPath()).toBe filePath2

       describe "when the project is selected", ->
         it "doesn't display the move dialog", ->
@@ -3494,13 +3559,13 @@

     describe "when dragging a FileView onto a DirectoryView's header", ->
       it "should add the selected class to the DirectoryView", ->
-        # Dragging theta onto alphaDir
+        # Dragging delta.txt onto alphaDir
         alphaDir = findDirectoryContainingText(treeView.roots[0], 'alpha')
         alphaDir.expand()

         gammaDir = findDirectoryContainingText(treeView.roots[0], 'gamma')
         gammaDir.expand()
-        deltaFile = gammaDir.entries.children[2]
+        deltaFile = gammaDir.entries.children[1]

         [dragStartEvent, dragEnterEvent, dropEvent] =
             eventHelpers.buildInternalDragEvents(deltaFile, alphaDir.querySelector('.header'))
@@ -3524,23 +3589,56 @@

         gammaDir = findDirectoryContainingText(treeView.roots[0], 'gamma')
         gammaDir.expand()
-        deltaFile = gammaDir.entries.children[2]
+        deltaFile = gammaDir.entries.children[1]

         [dragStartEvent, dragEnterEvent, dropEvent] =
             eventHelpers.buildInternalDragEvents(deltaFile, alphaDir.querySelector('.header'), alphaDir)

-        runs ->
+        treeView.onDragStart(dragStartEvent)
+        treeView.onDrop(dropEvent)
+        expect(alphaDir.children.length).toBe 2
+
+        waitsFor "directory view contents to refresh", ->
+          findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length > 2
+
+        runs ->
+          expect(findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length).toBe 3
+
+      it "shouldn't update editors with similar file paths", ->
+        deltaFilePath2 = path.join(gammaDirPath, 'delta.txt2')
+        fs.writeFileSync(deltaFilePath2, 'copy')
+
+        waitForWorkspaceOpenEvent ->
+          atom.workspace.open(deltaFilePath)
+
+        waitForWorkspaceOpenEvent ->
+          atom.workspace.open(deltaFilePath2)
+
+        runs ->
+          # Dragging delta.txt onto alphaDir
+          alphaDir = findDirectoryContainingText(treeView.roots[0], 'alpha')
+          alphaDir.expand()
+
+          gammaDir = findDirectoryContainingText(treeView.roots[0], 'gamma')
+          gammaDir.expand()
+          deltaFile = gammaDir.entries.children[1]
+
+          [dragStartEvent, dragEnterEvent, dropEvent] =
+              eventHelpers.buildInternalDragEvents(deltaFile, alphaDir.querySelector('.header'), alphaDir)
+
           treeView.onDragStart(dragStartEvent)
           treeView.onDrop(dropEvent)
           expect(alphaDir.children.length).toBe 2

-        waitsFor "directory view contents to refresh", ->
-          findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length > 2
-
-        runs ->
-          expect(findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length).toBe 3
+          editors = atom.workspace.getTextEditors()
+          expect(editors[0].getPath()).toBe deltaFilePath.replace('gamma', 'alpha')
+          expect(editors[1].getPath()).toBe deltaFilePath2

     describe "when dropping a DirectoryView onto a DirectoryView's header", ->
+      beforeEach ->
+        waitForWorkspaceOpenEvent ->
+          atom.workspace.open(thetaFilePath)
+
       it "should move the directory to the hovered directory", ->
         # Dragging thetaDir onto alphaDir
         alphaDir = findDirectoryContainingText(treeView.roots[0], 'alpha')
@@ -3551,15 +3649,11 @@
         thetaDir = gammaDir.entries.children[0]
         thetaDir.expand()

-        waitForWorkspaceOpenEvent ->
-          atom.workspace.open(thetaFilePath)
-
-        runs ->
-          [dragStartEvent, dragEnterEvent, dropEvent] =
-            eventHelpers.buildInternalDragEvents(thetaDir, alphaDir.querySelector('.header'), alphaDir)
-          treeView.onDragStart(dragStartEvent)
-          treeView.onDrop(dropEvent)
-          expect(alphaDir.children.length).toBe 2
+        [dragStartEvent, dragEnterEvent, dropEvent] =
+          eventHelpers.buildInternalDragEvents(thetaDir, alphaDir.querySelector('.header'), alphaDir)
+        treeView.onDragStart(dragStartEvent)
+        treeView.onDrop(dropEvent)
+        expect(alphaDir.children.length).toBe 2

         waitsFor "directory view contents to refresh", ->
           findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length > 2
@@ -3568,6 +3662,39 @@
           expect(findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length).toBe 3
           editor = atom.workspace.getActiveTextEditor()
           expect(editor.getPath()).toBe(thetaFilePath.replace('gamma', 'alpha'))
+
+      it "shouldn't update editors with similar file paths", ->
+        thetaDir2Path = path.join(gammaDirPath, 'theta2')
+        fs.makeTreeSync(thetaDir2Path)
+        thetaFilePath2 = path.join(thetaDir2Path, 'theta.txt2')
+        fs.writeFileSync(thetaFilePath2, 'copy')
+
+        waitForWorkspaceOpenEvent ->
+          atom.workspace.open(thetaFilePath2)
+
+        runs ->
+          # Dragging thetaDir onto alphaDir
+          alphaDir = findDirectoryContainingText(treeView.roots[0], 'alpha')
+          alphaDir.expand()
+
+          gammaDir = findDirectoryContainingText(treeView.roots[0], 'gamma')
+          gammaDir.expand()
+          thetaDir = gammaDir.entries.children[0]
+          thetaDir.expand()
+
+          waitForWorkspaceOpenEvent ->
+            atom.workspace.open(thetaFilePath)
+
+          runs ->
+            [dragStartEvent, dragEnterEvent, dropEvent] =
+              eventHelpers.buildInternalDragEvents(thetaDir, alphaDir.querySelector('.header'), alphaDir)
+            treeView.onDragStart(dragStartEvent)
+            treeView.onDrop(dropEvent)
+            expect(alphaDir.children.length).toBe 2
+
+            editors = atom.workspace.getTextEditors()
+            expect(editors[0].getPath()).toBe thetaFilePath.replace('gamma', 'alpha')
+            expect(editors[1].getPath()).toBe thetaFilePath2

     describe "when dragging a file from the OS onto a DirectoryView's header", ->
       it "should move the file to the hovered directory", ->


Just test copy because it turns out the for loop wasn't working anyway

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -1541,67 +1541,66 @@
         LocalStorage.clear()
         atom.notifications.clear()

-      for operation in ['copy', 'cut']
-        describe "when attempting to #{operation} and paste a directory into itself", ->
-          it "shows a warning notification and does not paste", ->
-            # /dir-1/ -> /dir-1/
-            LocalStorage["tree-view:#{operation}Path"] = JSON.stringify([dirPath])
-            newPath = path.join(dirPath, path.basename(dirPath))
-            dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-            expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
-            expect(fs.existsSync(newPath)).toBe false
-            expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Cannot paste a folder into itself'
-
-        describe "when attempting to #{operation} and paste a directory into a nested child directory", ->
-          it "shows a warning notification and does not paste", ->
-            nestedPath = path.join(dirPath, 'nested')
-            fs.makeTreeSync(nestedPath)
-
-            # /dir-1/ -> /dir-1/nested/
-            LocalStorage["tree-view:#{operation}Path"] = JSON.stringify([dirPath])
-            newPath = path.join(nestedPath, path.basename(dirPath))
-            dirView.reload()
-            nestedView = dirView.querySelector('.directory')
-            nestedView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-            expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
-            expect(fs.existsSync(newPath)).toBe false
-            expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Cannot paste a folder into itself'
-
-        describe "when attempting to #{operation} and paste a directory into a sibling directory that starts with the same letter", ->
-          it "allows the paste to occur", ->
-            # /dir-1/ -> /dir-2/
-            LocalStorage["tree-view:#{operation}Path"] = JSON.stringify([dirPath])
-            newPath = path.join(dirPath2, path.basename(dirPath))
-            dirView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-            expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
-            expect(fs.existsSync(newPath)).toBe true
-            expect(atom.notifications.getNotifications()[0]).toBeUndefined()
-
-        describe "when attempting to #{operation} and paste a directory into a symlink of itself", ->
-          it "shows a warning notification and does not paste", ->
-            fs.symlinkSync(dirPath, path.join(rootDirPath, 'symdir'), 'junction')
-
-            # /dir-1/ -> symlink of /dir-1/
-            LocalStorage["tree-view:#{operation}Path"] = JSON.stringify([dirPath])
-            newPath = path.join(dirPath, path.basename(dirPath))
-            symlinkView = root1.querySelector('.directory')
-            symlinkView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-            expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
-            expect(fs.existsSync(newPath)).toBe false
-            expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Cannot paste a folder into itself'
-
-        describe "when attempting to #{operation} and paste a symlink into its target directory", ->
-          it "allows the paste to occur", ->
-            symlinkedPath = path.join(rootDirPath, 'symdir')
-            fs.symlinkSync(dirPath, symlinkedPath, 'junction')
-
-            # symlink of /dir-1/ -> /dir-1/
-            LocalStorage["tree-view:#{operation}Path"] = JSON.stringify([symlinkedPath])
-            newPath = path.join(dirPath, path.basename(symlinkedPath))
-            dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-            expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
-            expect(fs.existsSync(newPath)).toBe true
-            expect(atom.notifications.getNotifications()[0]).toBeUndefined()
+      describe "when attempting to paste a directory into itself", ->
+        it "shows a warning notification and does not paste", ->
+          # /dir-1/ -> /dir-1/
+          LocalStorage["tree-view:copyPath"] = JSON.stringify([dirPath])
+          newPath = path.join(dirPath, path.basename(dirPath))
+          dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+          expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
+          expect(fs.existsSync(newPath)).toBe false
+          expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Cannot copy a folder into itself'
+
+      describe "when attempting to paste a directory into a nested child directory", ->
+        it "shows a warning notification and does not paste", ->
+          nestedPath = path.join(dirPath, 'nested')
+          fs.makeTreeSync(nestedPath)
+
+          # /dir-1/ -> /dir-1/nested/
+          LocalStorage["tree-view:copyPath"] = JSON.stringify([dirPath])
+          newPath = path.join(nestedPath, path.basename(dirPath))
+          dirView.reload()
+          nestedView = dirView.querySelector('.directory')
+          nestedView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+          expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
+          expect(fs.existsSync(newPath)).toBe false
+          expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Cannot copy a folder into itself'
+
+      describe "when attempting to paste a directory into a sibling directory that starts with the same letter", ->
+        it "allows the paste to occur", ->
+          # /dir-1/ -> /dir-2/
+          LocalStorage["tree-view:copyPath"] = JSON.stringify([dirPath])
+          newPath = path.join(dirPath2, path.basename(dirPath))
+          dirView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+          expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
+          expect(fs.existsSync(newPath)).toBe true
+          expect(atom.notifications.getNotifications()[0]).toBeUndefined()
+
+      describe "when attempting to paste a directory into a symlink of itself", ->
+        it "shows a warning notification and does not paste", ->
+          fs.symlinkSync(dirPath, path.join(rootDirPath, 'symdir'), 'junction')
+
+          # /dir-1/ -> symlink of /dir-1/
+          LocalStorage["tree-view:copyPath"] = JSON.stringify([dirPath])
+          newPath = path.join(dirPath, path.basename(dirPath))
+          symlinkView = root1.querySelector('.directory')
+          symlinkView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+          expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
+          expect(fs.existsSync(newPath)).toBe false
+          expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Cannot copy a folder into itself'
+
+      describe "when attempting to paste a symlink into its target directory", ->
+        it "allows the paste to occur", ->
+          symlinkedPath = path.join(rootDirPath, 'symdir')
+          fs.symlinkSync(dirPath, symlinkedPath, 'junction')
+
+          # symlink of /dir-1/ -> /dir-1/
+          LocalStorage["tree-view:copyPath"] = JSON.stringify([symlinkedPath])
+          newPath = path.join(dirPath, path.basename(symlinkedPath))
+          dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
+          expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow()
+          expect(fs.existsSync(newPath)).toBe true
+          expect(atom.notifications.getNotifications()[0]).toBeUndefined()

       describe "when pasting entries which don't exist anymore", ->
         it "skips the entry which doesn't exist", ->


new line at end of file, trying to force travis to run again--trying to see if travis gave a false-positive

--- src/components/task-step/ends.cjsx
+++ src/components/task-step/ends.cjsx
@@ -44,4 +44,4 @@
       </BS.Panel>
     </div>

-module.exports = {task: TaskEnd, homework: HomeworkEnd, practice: PracticeEnd}+module.exports = {task: TaskEnd, homework: HomeworkEnd, practice: PracticeEnd}


Forward config events based on node property

--- src/configuration-generator.coffee
+++ src/configuration-generator.coffee
@@ -192,7 +192,7 @@
         composedConfig.templateId = templateId
         composedConfig.transactionGroupId = transactionGroupId if linkedToData?
         composedConfig.instanceId = instanceId
-
+        composedConfig.eventType = config.eventType
         instanceMap[instanceId] = composedConfig

     return instanceMap
@@ -254,7 +254,10 @@
       nodeLinks = _.filter links, from: config.nodeUuid
       templateLinks = config.linkedTo
       linkedTo = []
-      eventLinks = {}
+      eventLinks =
+        'broadcast.sent': []
+        'message.received': []
+        'configure.sent': []

       if config.linkedToInput
         result[config.nodeUuid] ?=
@@ -276,13 +279,14 @@

       if config.linkedToNext
         _.each nodeLinks, (link) =>
-          nodesToAdd = _.pluck _.filter(instanceMap, nodeUuid: link.to, linkedToPrev: true), 'instanceId'
-          if link.type?
-            eventLinks[link.type] = [].concat eventLinks[link.type] || [], nodesToAdd
-          else
-            #old behavior
-            eventLinks['broadcast.sent'] = [].concat eventLinks['broadcast.sent'] || [], nodesToAdd
-            eventLinks['message.received'] = [].concat eventLinks['message.received'] || [], nodesToAdd
+          prevNanocytes = _.filter instanceMap, nodeUuid: link.to, linkedToPrev: true
+          _.each prevNanocytes, (nanocyte) =>
+            if config.eventType == 'configure'
+              eventLinks['configure.sent'].push nanocyte.instanceId
+            else
+              eventLinks['broadcast.sent'].push nanocyte.instanceId
+              eventLinks['message.received'].push nanocyte.instanceId
+

       _.each config.linkedTo, (templateLinkId) =>
         _.each instanceMap, (data, key) =>


Updated autocomplete comments.

Updated autocomplete comments.

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -11,20 +11,18 @@
 ## @attributes
 ## - mac-autocomplete-url:             url to fetch autocomplete dropdown list data
 ## - mac-autocomplete-on-select:       function called when user select on an item
-##                                     @params:
-##                                        selected - the item selected
+##                                       @params {Object} selected The item selected
 ## - mac-autocomplete-on-success:      function called on success ajax request
-##                                     @params:
-##                                        data   - data returned from the request
-##                                        others - status, headers
+##                                       @params {Object} data Data returned from the request
+##                                       @params {Number} status The status code of the response
+##                                       @params {Object} headeres Header of the response
 ## - mac-autocomplete-on-error:        function called on ajax request error
-##                                     @params:
-##                                        data   - data returned from the request
-##                                        others - status, headers
+##                                       @params {Object} data Data returned from the request
+##                                       @params {Number} status The status code of the response
+##                                       @params {Object} headeres Header of the response
 ## - mac-autocomplete-on-key-down:     function called on key down
-##                                     @params:
-##                                        event - jQuery event
-##                                        value - the value in the input text
+##                                       @params {Object} event jQuery event
+##                                       @params {String} value Value in the input text
 ## - mac-autocomplete-value:           the value to be sent back upon selection               (default "id")
 ## - mac-autocomplete-label:           the label to display to the users                      (default "name")
 ## - mac-autocomplete-query:           the query parameter on GET command                     (default "q")


clicking controls from title screen works

--- canvas/game.coffee
+++ canvas/game.coffee
@@ -1176,24 +1176,21 @@
       @current = new ControlsScene(this, @engine)

   class ControlsScene
-    constructor: (@gw, @window) ->
-      @img = pyglet.resource.image("data/howtoplay.png")
-      @window.set_handler('on_draw', @on_draw)
-      @window.set_handler('on_mouse_press', @on_mouse_press)
-      pyglet.clock.schedule_interval(@update, 1/game_fps)
-
-    update: (dt) ->
-
-    on_draw: ->
-      @window.clear()
-      @img.blit(0, 0)
+    constructor: (@gw, @engine) ->
+      @batch = new Engine.Batch()
+      @img = new Engine.Sprite("howtoplay", batch: @batch)
+      @engine.on('draw', @draw)
+      @engine.on('mousedown', @onMouseDown)
+
+    draw: =>
+      @engine.clear()
+      @engine.draw @batch

     end: ->
-      @window.remove_handler('on_draw', @on_draw)
-      @window.remove_handler('on_mouse_press', @on_mouse_press)
-      pyglet.clock.unschedule(@update)
-
-    on_mouse_press: (x, y, button, modifiers) ->
+      @engine.removeListener('draw', @draw)
+      @engine.removeListener('mousedown', @onMouseDown)
+
+    onMouseDown: =>
       @gw.title()


Fix typo

Fix typo

--- index.cjsx
+++ index.cjsx
@@ -230,7 +230,7 @@
     componentWillUnmount: ->
       window.removeEventListener 'game.response', @handleResponse
     render: ->
-

+

         <link rel="stylesheet" href={join(relative(ROOT, __dirname), 'assets', 'exp-calc.css')} />
         <Grid>
           <Col xs={shipRow}>


Export mapspot data into plugin store

Export mapspot data into plugin store

--- index.cjsx
+++ index.cjsx
@@ -10,6 +10,7 @@
 {APPDATA_PATH, SERVER_HOSTNAME} = window
 __ = window.i18n["poi-plugin-prophet"].__.bind(window.i18n["poi-plugin-prophet"])

+{store} = require 'views/create-store'
 BottomAlert = require './parts/bottom-alert'
 ProphetPanel = require './parts/prophet-panel'

@@ -394,12 +395,20 @@
   description: __ "Sortie Prophet"
   author: 'Chiba'
   link: 'https://github.com/Chibaheit'
+  reducer: (state={}, action) ->
+    if (action.type == '@@poi-plugin-prophet/updateMapspot')
+      return mapspot: action.data
+    state
+
   reactClass: React.createClass
     getInitialState: ->
       # Load map data
       mapspot = null
       try
         mapspot = CSON.parseCSONFile join(__dirname, 'assets', 'data', 'mapspot.cson')
+        store.dispatch
+          type: '@@poi-plugin-prophet/updateMapspot'
+          data: mapspot
       catch
         console.log 'Failed to load map data!'


:bug: [linter-registry] Keep buffer modifiying linters out of it

--- lib/linter-registry.coffee
+++ lib/linter-registry.coffee
@@ -65,7 +65,7 @@
       resolve(linter.lint(editor))
     ).then((results) =>
       toReturn = {linter, messages: results, editor}
-      return toReturn if @batchUpdateMessages
+      return toReturn if @batchUpdateMessages and not linter.modifiesBuffer
       @emitter.emit('did-update-messages', toReturn)
     ).catch((e) -> helpers.error(e))


:bug: Fix on OperationStack::finish  argument for Promiseed execution

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -84,7 +84,7 @@
   execute: (operation) ->
     execution = operation.execute()
     if execution instanceof Promise
-      onResolve = @finish.bind(this, [operation])
+      onResolve = @finish.bind(this, operation)
       onReject = @handleError.bind(this)
       execution.then(onResolve).catch(onReject)
     else


Fix composition on input operators

Fix composition on input operators

--- lib/operators/input.coffee
+++ lib/operators/input.coffee
@@ -14,7 +14,7 @@
 class Input extends Operator
   standalone: true

-  isComplete: -> @standalone || @composed
+  isComplete: -> @standalone || super

   confirmTransaction: (transaction) ->
     bundler = new TransactionBundler(transaction)


use debouncedPreview instead of delegate to onStopChanging timing

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -42,10 +42,10 @@

   stopRefreshingDelay: 100
   stopRefreshingTimeout: null
+  debouncedPreviewDelay: 100

   autoPreview: null
   autoPreviewOnQueryChange: null
-  autoPreviewOnNextStopChanging: false

   preventSyncToEditor: false
   ignoreChange: false
@@ -203,7 +203,6 @@
     @disposables.add(
       @registerCommands()
       @observeChange()
-      @observeStopChanging()
       @observeCursorMove()
       @observeStopChangingActivePaneItem()
     )
@@ -457,11 +456,12 @@
       range = @editor.setTextInBufferRange(itemArea, texts.join("\n"), undo: 'skip')
       @editorLastRow = range.end.row

-  observeStopChanging: ->
-    @editor.onDidStopChanging =>
-      if @autoPreviewOnNextStopChanging
-        @preview()
-        @autoPreviewOnNextStopChanging = false
+  debouncedPreview: ->
+    clearTimeout(@debouncedPreviewTimeout) if @debouncedPreviewTimeout?
+    preview = =>
+      @debouncedPreviewTimeout = null
+      @preview()
+    @debouncedPreviewTimeout = setTimeout(preview, @debouncedPreviewDelay)

   observeChange: ->
     @editor.buffer.onDidChange ({newRange, oldRange}) =>
@@ -485,8 +485,7 @@
               if @provider.boundToEditor
                 @preview()
               else
-                # Delay immediate preview unless @provider is boundToEditor
-                @autoPreviewOnNextStopChanging = true
+                @debouncedPreview()
       else
         @setModifiedState(true)


Various fixes including regex.

Various fixes including regex.

--- lib/project-checker.coffee
+++ lib/project-checker.coffee
@@ -122,7 +122,8 @@
     if m
       # Build up the regex from the components. We can't handle "g" in the flags,
       # so quietly remove it.
-      f = m[2].replace("g", "")
+      f = m[2].replace "g", ""
+      f = f.replace "y", ""
       r = new RegExp m[1], f
       { regex: r, text: m[1], flags: f }
     else


tweak

tweak

--- lib/runtime/console.coffee
+++ lib/runtime/console.coffee
@@ -57,15 +57,15 @@
   reset: -> @c.reset()

   eval: ({editor, mode}) ->
-    if editor.getText().trim()
-      client.boot()
-      @c.logInput()
-      @c.done()
-      evalrepl(code: editor.getText(), mode: mode?.name, mod: modules.currentModule())
-        .then (result) =>
-          @c.input()
-          notifications.show "Evaluation Finished"
-        .catch => @c.input()
+    return unless editor.getText().trim()
+    client.boot()
+    @c.logInput()
+    @c.done()
+    evalrepl(code: editor.getText(), mode: mode?.name, mod: modules.currentModule())
+      .then (result) =>
+        @c.input()
+        notifications.show "Evaluation Finished"
+      .catch => @c.input()

   modes: [
     {name: 'julia', default: true, grammar: 'source.julia'}


Add some debug logging for socket connections

--- lib/web/scripts/App.coffee
+++ lib/web/scripts/App.coffee
@@ -20,6 +20,10 @@

   setupSocket: ->
     @socket = io()
+    @socket.on 'connect', => @log 'Socket connected'
+    @socket.on 'disconnect', => @log 'Socket disconnected!'
+    @socket.on 'reconnect', => @log 'Socket reconnected'
+    @socket.on 'reconnect_failed', => @log 'Socket gave up reconnecting!!'
     @socket.on 'control:change', (controlJSON) =>
       control = @controls.get(controlJSON.id)
       unless control?


Hide subchannels when showing all factlinks in topic

--- core/app/backbone/views/channel_view.coffee
+++ core/app/backbone/views/channel_view.coffee
@@ -29,8 +29,11 @@
     choice = @$('.js-channel-topic-switch').val()
     if choice == 'topic'
       @showFacts @topicFacts()
+      @$('.js-region-sub-channels').hide()
     else
       @showFacts @channelFacts()
+      @$('.js-region-sub-channels').show()
+

   showFacts: (facts) ->
     @factList.show new FactsView


fix konnector controller

fix konnector controller

--- server/controllers/konnectors.coffee
+++ server/controllers/konnectors.coffee
@@ -38,7 +38,7 @@
                     konnector.modelNames = modelNames

                 konnectors.sort (konnectorA, konnectorB) ->
-                    konnector.name.localeCompare konnectorB.name
+                    konnectorA.name.localeCompare konnectorB.name

                 res.send konnectors


call handleTimeout function

call handleTimeout function

--- server/controllers/konnectors.coffee
+++ server/controllers/konnectors.coffee
@@ -48,6 +48,10 @@

     import: (req, res, next) ->
+        # Handle timeouts
+        poller = require "../lib/konnector_poller"
+        poller.handleTimeout(req.body)
+
         if req.konnector.isImporting
             setTimeout =>
                 data =


Removed the needless session reload, and moved the sandbox release up so it reads nicer.

--- server/packages/angular/index.coffee
+++ server/packages/angular/index.coffee
@@ -72,14 +72,13 @@

 			).then(->

-				# Reload the session, server-side JS socket stuff could
-				# have changed it!
-				Promise.promisify(req.session.reload, req.session)()
-
-			).then(->
-
 				emission = @sandbox.emitHtml()

+				# Let any sandbox expirations take place now that we've
+				# emitted.
+				@deferred.resolve()
+				@sandbox.setBusy null
+
 				# If a redirect happened in the sandbox, actually redirect the
 				# browser and save the emission for the next request.
 				if (redirectionPath = @sandbox.redirectionPath())?
@@ -93,11 +92,6 @@
 					# Emit.
 					res.end emission

-				# Let any sandbox expirations take place now that we've
-				# emitted.
-				@deferred.resolve()
-				@sandbox.setBusy null
-
 			).catch(ResponseComplete, ->

 			).catch (error) -> next error


Show at least 5% of the bar so it's always visible

--- src/components/learning-guide/progress-bar.cjsx
+++ src/components/learning-guide/progress-bar.cjsx
@@ -17,7 +17,7 @@
       percent = Math.round((section.current_level / 1) * 100)
       console.log section
       # always show at least 5% of bar, otherwise it just looks empty
-      <BS.ProgressBar className={section.interpretation} now={percent} />
+      <BS.ProgressBar className={section.interpretation} now={Math.max(percent, 5)} />
     else
       Practice section


atom: remove traps of term3 package

atom: remove traps of term3 package

--- .atom/keymap.cson
+++ .atom/keymap.cson
@@ -19,17 +19,10 @@
 # You can find more information about keymaps in these guides:
 # * https://atom.io/docs/latest/using-atom-basic-customization#customizing-key-bindings
 # * https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-'atom-workspace':
-  'ctrl-`': 'term3:open'

 # revert default atom behaviour that was redefined by vim-mode-plus
 'atom-workspace atom-text-editor':
   'ctrl-f': 'find-and-replace:show'
-
-'atom-workspace .term3.term3':
-  'ctrl-p': 'unset!'
-  'ctrl-insert': 'term3:copy'
-  'shift-insert': 'term3:paste'

 'atom-workspace atom-text-editor:not([mini]).autocomplete-active':
   'alt-k': 'core:move-up'


atom: update settings minorly

atom: update settings minorly

--- .atom/keymap.cson
+++ .atom/keymap.cson
@@ -50,6 +50,7 @@
 # git-blame toogle to work with vim-mode
 'atom-text-editor':
   'alt-ctrl-b': 'git-blame:toggle'
+  'ctrl-shift-H': 'atom-html-preview:toggle'

 # change keymap to prevent conflict with "open file" functionality
 'atom-workspace atom-text-editor:not([mini])':


Fix keybindings

Fix keybindings

--- keymaps/highlight-line.cson
+++ keymaps/highlight-line.cson
@@ -1,5 +1,5 @@
-'.atom-text-editor':
+'atom-text-editor':
   'cmd-shift-u': 'highlight-line:toggle-underline'
   'cmd-alt-shift-u': 'highlight-line:toggle-selection-borders'
   'cmd-shift-h': 'highlight-line:toggle-background'
-  'cmd-alt-shift-h' : 'highlight-line:toggle-hide-highlight-on-select'
+  'cmd-alt-shift-h': 'highlight-line:toggle-hide-highlight-on-select'


Fix location for calculation, fix arguments

--- app/scripts/components/shaders/Gradient.coffee
+++ app/scripts/components/shaders/Gradient.coffee
@@ -5,6 +5,7 @@
   attribute vec4 aColor;

   varying lowp vec4 vColor;
+  varying lowp vec2 vLayer;
   uniform  vec4 uViewport;

   mat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0,    0, -2.0 / uViewport.w, 0,0,    0, 0,1,0,    -1,+1,0,1);
@@ -18,15 +19,17 @@
     pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin;
     gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );

-    vColor = vec4(aColor.rgb*aColor.a*aLayer.y, aColor.a*aLayer.y);
+    vColor = aColor;
+    vLayer = aLayer;
   }
 """

 GRADIENT_FRAGMENT_SHADER = """
   precision mediump float;
   varying lowp vec4 vColor;
+  varying lowp vec2 vLayer;
   void main(void) {
-    gl_FragColor = vColor;
+    gl_FragColor = vec4(vColor.rgb*vColor.a*vLayer.y, vColor.a*vLayer.y);
   }
 """

@@ -54,7 +57,7 @@
   init: ->
     @bind 'Draw', @_drawGradient
     if @has 'WebGL'
-      @_establishShader 'Gradient',
+      @_establishShader "Gradient",
         GRADIENT_FRAGMENT_SHADER,
         GRADIENT_VERTEX_SHADER,
         GRADIENT_ATTRIBUTE_LIST
@@ -69,15 +72,15 @@
   _drawGradient: (e) ->
     if e.type is 'webgl'
       e.program.writeVector('aColor',
-          @_topColor._red/255,
-          @_topColor._green/255,
-          @_topColor._blue/255,
-          @_topColor._strength,
+        @_topColor._red/255,
+        @_topColor._green/255,
+        @_topColor._blue/255,
+        @_topColor._strength,

-          @_bottomColor._red/255,
-          @_bottomColor._green/255,
-          @_bottomColor._blue/255,
-          @_bottomColor._strength
+        @_bottomColor._red/255,
+        @_bottomColor._green/255,
+        @_bottomColor._blue/255,
+        @_bottomColor._strength
       )

   topColor: (color) ->
@@ -89,17 +92,17 @@
     @_setColor(@_bottomColor, arguments...)

   _setColor: (varColor, color) ->
-    if arguments.length >= 3
-      varColor._red = arguments[0]
-      varColor._green = arguments[1]
-      varColor._blue = arguments[2]
-      varColor._strength = arguments[3] if typeof arguments[3] is 'number'
+    if arguments.length >= 4
+      varColor._red = arguments[1]
+      varColor._green = arguments[2]
+      varColor._blue = arguments[3]
+      varColor._strength = arguments[4] if typeof arguments[4] is 'number'
     else
       # First argument is color name
       Crafty.assignColor(color, varColor)
       # Second argument, if present, is strength of color
       # Note that assignColor will give a default strength of 1.0 if none exists.
-      varColor._strength = arguments[1] if typeof arguments[1] is 'number'
+      varColor._strength = arguments[2] if typeof arguments[2] is 'number'

     @trigger 'Invalidate'
     this


Uncommented an header to activate translations

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -990,7 +990,7 @@
     status_unsubscribed_active: "你还没有订阅所以不会被收费，但你的帐号还可以正常使用"
     status_unsubscribed: "只要一个CodeCombat订阅，你就可以玩转全部新关卡，用遍全部英雄，物品，以及获得更多的宝石哦！"

-#  account_invoices:
+  account_invoices:
     amount: "金额（美元）"
     declined: "您的付款被拒"
     invalid_amount: "请输入美元金额"


Prefer operand 'isnt' over '!='.

Prefer operand 'isnt' over '!='.

--- src/coffee/utils/order-utils.coffee
+++ src/coffee/utils/order-utils.coffee
@@ -40,7 +40,7 @@
     if deliveriesDiffs
       # iterate over returnInfo instances
       _.each _.keys(deliveriesDiffs), (deliveryIndex) ->
-        if deliveryIndex != '_t'
+        if deliveryIndex isnt '_t'
           deliveryDiff = deliveriesDiffs[deliveryIndex]
           if _.isArray deliveryDiff
             # delivery was added
@@ -54,7 +54,7 @@
           else
             # iterate over parcel instances
             _.each _.keys(deliveryDiff.parcels), (parcelIndex) ->
-              if parcelIndex != '_t'
+              if parcelIndex  isnt '_t'
                 parcelDiff = deliveryDiff.parcels[parcelIndex]

                 if _.isArray parcelDiff
@@ -84,7 +84,7 @@
     if returnInfoDiffs
       # iterate over returnInfo instances
       _.each _.keys(returnInfoDiffs), (returnInfoIndex) ->
-        if returnInfoIndex != '_t'
+        if returnInfoIndex isnt '_t'
           returnInfoDiff = returnInfoDiffs[returnInfoIndex]
           if _.isArray returnInfoDiff
             # returnInfo was added
@@ -101,7 +101,7 @@
             returnInfo = returnInfoDiff
             # iterate over returnInfo items instances
             _.each _.keys(returnInfo.items), (returnInfoItemIndex) ->
-              if returnInfoItemIndex != '_t'
+              if returnInfoItemIndex isnt '_t'
                 returnInfoItem = returnInfo.items[returnInfoItemIndex]
                 # iterate over all returnInfo status actions
                 _.each actionsListReturnInfoState(), (actionDefinition) ->


do code highlighting for code directive

do code highlighting for code directive

--- src/compiler/directives/code.coffee
+++ src/compiler/directives/code.coffee
@@ -1,6 +1,9 @@
 fs = require 'fs'
 path = require 'path'
 async = require 'async'
+
+hljs = require "highlight.js"
+modelist = require "../../utils/modelist"

 class Code
   constructor: (@path,@options) ->
@@ -10,10 +13,32 @@
     async.waterfall [
       (cb) => fs.readFile @path,{encoding: "utf8"}, cb
       (source,cb) =>
-        source = @hbs.escape source
+        source = @prepareSource(source)
         html = "
#{source}
"
         cb(null,@hbs.safe(html))
     ], cb
+
+  # Return the source code as html content. Highlight it there's a match highlighter.
+  # @return (HTML)
+  prepareSource: (source) ->
+    if lang = @languageName(@path)
+      code = @highlight(lang,source)
+    else
+      code = @hbs.escape source
+
+  # Uses ace editor's modelist to guess language name from a given filename.
+  # @return (String|null) the language name, or null if there's no associated mode.
+  languageName: (path) ->
+    match = modelist.getModeForPath(path)
+
+    if match.name == "text"
+      return null
+
+    return match.name
+
+  highlight: (lang,code) ->
+    hljs.highlight(lang,code).value
+


only on command dialog can be shown

only on command dialog can be shown

--- src/components/editor/editor.coffee
+++ src/components/editor/editor.coffee
@@ -34,16 +34,31 @@
         stylesheets: ["/lib/rich-editor.css"]
       @editor.on "load", =>
         syncContent @
+        controlDialogs @
         listenKeyDownActions @
         watchActivePageChangeAndSycnEditor @
         eventbus.emit "editor-loaded"
+
+controlDialogs = (vm)->
+  [imageCmdDom, imageDialog, linkCmdDom, linkDialog] = []
+  vm.editor.on "show:dialog", (cmd)->
+    if cmd.command is "insertImage"
+      [imageCmdDom, imageDialog] = [cmd.commandLink, cmd.dialogContainer]
+      if linkCmdDom
+        linkCmdDom.className = ""
+        linkDialog.style.display = "none"
+    else if cmd.command is "createLink"
+      [linkCmdDom, linkDialog] = [cmd. commandLink, cmd.dialogContainer]
+      if imageCmdDom
+        imageCmdDom.className = ""
+        imageDialog.style.display = "none"

 listenScrollToAjustEditorbar = (vm)->
   _scroll = window.onscroll
   window.onscroll = ->
     scrollTop = document.body.scrollTop or window.pageYOffset
     vm.floatBar = (scrollTop isnt 0)
-    if _scroll then _scroll.apply @, aruguments
+    if _scroll then _scroll.apply @, arguments

 syncContent = (vm)->
   sync = ->


Add documentation to the event system

Add documentation to the event system

--- app/core/Entity.coffee
+++ app/core/Entity.coffee
@@ -62,11 +62,20 @@
     @physBody.SetLinearVelocity(new @level.physicsManager.Vec2(0, 0))

+  ###
+  Adds an event listener.
+  @param [string] type The event type to listen to.
+  @param [function] listener The Callback to be invoked.
+  ###
   addListener: (type, listener) =>
     @_listeners[type] = [] unless @_listeners[type]?
     @_listeners[type].push listener

+  ###
+  Fires an event, invoking all it's listeners.
+  @param [string] event The event type.
+  ###
   fire: (event) =>
     event = type:event if typeof event is "string"
     event.target = this unless event.target?
@@ -88,6 +97,11 @@
         delete event.callback

+  ###
+  Removes an event listener.
+  @param [string] type The event type.
+  @param [function] listner The Callback to be removed.
+  ###
   removeListener: (type, listener) =>
     if @_listeners[type] instanceof Array
       listeners = @_listeners[type]


Redirect /etc -> /teachers/demo

Redirect /etc -> /teachers/demo

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -129,6 +129,8 @@
     'editor/course': go('editor/course/CourseSearchView')
     'editor/course/:courseID': go('editor/course/CourseEditView')

+    'etc': redirect('/teachers/demo')
+
     'file/*path': 'routeToServer'

     'github/*path': 'routeToServer'


Fix bug with new docs formatting

Fix bug with new docs formatting

--- lib/elixir-docs-view.coffee
+++ lib/elixir-docs-view.coffee
@@ -115,12 +115,17 @@

     @callbacks ||= "No callback information available."

-    @element.querySelector('.docsContent').innerHTML = markdownToHTML(@docs)
-    convertCodeBlocksToAtomEditors($('.docsContent')[0])
-    @element.querySelector('.typesContent').innerHTML = markdownToHTML(@types)
-    convertCodeBlocksToAtomEditors($('.typesContent')[0])
-    @element.querySelector('.callbacksContent').innerHTML = markdownToHTML(@callbacks)
-    convertCodeBlocksToAtomEditors($('.callbacksContent')[0])
+    docsElement = @element.querySelector('.docsContent')
+    docsElement.innerHTML = markdownToHTML(@docs)
+    convertCodeBlocksToAtomEditors(docsElement)
+
+    typesElement = @element.querySelector('.typesContent')
+    typesElement.innerHTML = markdownToHTML(@types)
+    convertCodeBlocksToAtomEditors(typesElement)
+
+    callbacksElement = @element.querySelector('.callbacksContent')
+    callbacksElement.innerHTML = markdownToHTML(@callbacks)
+    convertCodeBlocksToAtomEditors(callbacksElement)

     @renderMarkdown()


remove useless process import, fails on some noe versions

--- lib/ewg/haml/context.coffee
+++ lib/ewg/haml/context.coffee
@@ -3,7 +3,6 @@
 path    = require 'path'
 extend  = require 'extend'
 log     = require 'ewg-logging'
-process = require 'process'
 helper  = require('express-helpers')()
 Globals = require './globals'