Clarifying createFromUrl error when no redis

--- lib/content._coffee
+++ lib/content._coffee
@@ -197,7 +197,7 @@
         # TODO: Change this to an optimistic random ID (w/ retry on collision).
         # We need to make sure our writes support proper rollback too though.
         if not redisClient
-          throw new Error 'redis client not initialized'
+          throw new Error 'Content.createFromURL() not implemented yet w/out Redis.'

         id = (redisClient.incr NEXT_ID_KEY, _).toString()
         content = new Content {id, url}


Adding support for no local database for content-by-url

--- lib/content._coffee
+++ lib/content._coffee
@@ -12,7 +12,10 @@

 # TODO: this is a short term fix to support the content by url code-path
 # we should remove this when we move to a proper database
-DATABASE = require Path.join DIR_BY_URL_PATH, 'data.json'
+try
+    DATABASE = require Path.join DIR_BY_URL_PATH, 'data.json'
+catch e
+    DATABASE = {}

 DIR_PATH_FROM_URL_TO_ID = Path.relative DIR_BY_URL_PATH, DIR_BY_ID_PATH

@@ -41,7 +44,10 @@
     Path.join DIR_BY_ID_PATH, "#{id}.json"

 getFilePathForURL = (url) ->
-    Path.join DIR_BY_ID_PATH, DATABASE["#{hashURL url}.json"].replace "../content-by-id", ""
+    if idFile = DATABASE["#{hashURL url}.json"]
+        Path.join DIR_BY_ID_PATH, idFile.replace '../content-by-id', ''
+    else
+        ''

 getRedisKeyForId = (id) ->
     "content:id:#{id}"


Use atom.workspaceView.eachEditorView

Use atom.workspaceView.eachEditorView

--- lib/git-diff.coffee
+++ lib/git-diff.coffee
@@ -2,6 +2,6 @@

 module.exports =
   activate: ->
-    atom.workspaceView.eachEditor (editor) =>
+    atom.workspaceView.eachEditorView (editor) =>
       if atom.project.getRepo()? and editor.attached and editor.getPane()?
         new GitDiffView(editor)


Added the '-cd' argument to latexmk for compiling LaTeX files. This makes it possible to compile LaTeX files not in the project root, and still have relative referenced files working without errors. This closes #594.

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -191,7 +191,7 @@
   LaTeX:
     "File Based":
       command: "latexmk"
-      args: (context) -> ['-pv', '-quiet', '-pdf', '-shell-escape', context.filepath]
+      args: (context) -> ['-cd', '-quiet', '-pdf', '-pv', '-shell-escape', context.filepath]

   LilyPond:
     "File Based":


added C++ grammer to language C++14 too

added C++ grammer to language C++14 too

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -156,6 +156,26 @@
         command: "bash"
         args: (context) -> ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '/mnt/" + path.posix.join.apply(path.posix, [].concat([context.filepath.split(path.win32.sep)[0].toLowerCase()], context.filepath.split(path.win32.sep).slice(1))).replace(":", "") + "' -o /tmp/cpp.out && /tmp/cpp.out"]

+  'C++14':
+    if GrammarUtils.OperatingSystem.isDarwin()
+      "File Based":
+        command: "bash"
+        args: (context) -> ['-c', "xcrun clang++ -fcolor-diagnostics -std=c++14 -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+    else if GrammarUtils.OperatingSystem.isLinux()
+      "Selection Based":
+        command: "bash"
+        args: (context) ->
+          code = context.getCode(true)
+          tmpFile = GrammarUtils.createTempFileWithCode(code, ".cpp")
+          ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '" + tmpFile + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+      "File Based":
+        command: "bash"
+        args: (context) -> ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+    else if GrammarUtils.OperatingSystem.isWindows() and GrammarUtils.OperatingSystem.release().split(".").slice -1 >= '14399'
+      "File Based":
+        command: "bash"
+        args: (context) -> ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '/mnt/" + path.posix.join.apply(path.posix, [].concat([context.filepath.split(path.win32.sep)[0].toLowerCase()], context.filepath.split(path.win32.sep).slice(1))).replace(":", "") + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+
   Clojure:
     "Selection Based":
       command: "lein"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -1,6 +1,6 @@
 module 'Metered'

-test 'should exist', ->
+test "should exist", ->
   obj = GameObject()
   obj.include(Metered)


Fix bug in auto-closing messages startup code

Re-order to make finishLastDiff available; don't clobber bootstrap

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -411,18 +411,6 @@
 #  @messageSummary = (root) ->
 #    root = root._id if root._id?
 #    MessagesSummary.findOne root
-
-if Meteor.isServer
-  ## Remove all editors on server start, so that we can restart listeners.
-  ## Update finished field accordingly.
-  Messages.find().forEach (message) ->
-    if message.editing?.length
-      finishLastDiff message._id, message.editing
-      Messages.update message._id,
-        $unset: editing: ''
-
-  onExit ->
-    console.log 'EXITING'

 @canSee = (message, client = Meteor.isClient, user = Meteor.user(), group) ->
   ## Visibility of a message is implied by its existence in the Meteor.publish
@@ -928,6 +916,18 @@
 if Meteor.isServer
   editorTimers = {}
   stopTimers = {}
+
+  ## Remove all editors on server start, so that we can restart listeners.
+  ## Update finished field accordingly, but don't prevent finished bootstrap.
+  needBootstrap = not MessagesDiff.findOne finished: $exists: true
+  Messages.find().forEach (message) ->
+    if message.editing?.length
+      finishLastDiff message._id, message.editing unless needBootstrap
+      Messages.update message._id,
+        $unset: editing: ''
+
+  onExit ->
+    console.log 'EXITING'

   ## If we're using a persistent store for sharejs, we need to cleanup
   ## leftover documents from last time.  This should only be for local


fix occurrence not found case

fix occurrence not found case

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -112,8 +112,9 @@
         if ranges.length
           @editor.setSelectedBufferRanges(ranges)
         else
-          @editor.clearSelections() # FIXME when occurrence not found.
-      @patternForOccurence = null if @resetPatternForOccurence
+          @restorePoint(selection) for selection in @editor.getSelections()
+          @abort()
+        @patternForOccurence = null if @resetPatternForOccurence

     markerForTrackChange = null
     @onDidSelectTarget =>


keep setup note open while loading

keep setup note open while loading

--- lib/packages.coffee
+++ lib/packages.coffee
@@ -41,38 +41,43 @@
       @install pkg, =>
         @installAll pkgs
     else
+      @setupnote.dismiss()
       atom.notifications.addSuccess "Juno: Success!",
         detail: "We've set up the Atom packages for you."
       cb?()

   setupInfo: ->
-    atom.notifications.addInfo "Juno: Installing Atom packages",
+    @setupnote = atom.notifications.addInfo "Juno: Installing Atom packages",
       detail: """
         This will take a moment – hang tight!
+        We'll let you know once it's done.
         """
+      dismissable: true

   settingsError: ->
     atom.notifications.addError "Juno: Couldn't find settings-view package.",
       dismissable: true

   retreiveError: (name, err) ->
+    @setupnote.dismiss()
     atom.notifications.addError "Juno: Error downloading package info for #{name}",
     detail: """
       Please check your internet connection, or report this to
           http://discuss.junolab.org
       and we'll try to help.
-          ---
+          –––
       #{err}
       """
     dismissable: true

   installError: (name, err) ->
+    @setupnote.dismiss()
     atom.notifications.addError "Juno: Error installing package #{name}",
     detail: """
       Please check your internet connection, or report this to
           http://discuss.junolab.org
       and we'll try to help.
-          ---
+          –––
       #{err}
       """
     dismissable: true


use selectedIndex instead of selectedOptions to detect empty selects

--- app/assets/javascripts/tenon/features/record_list_updater.js.coffee
+++ app/assets/javascripts/tenon/features/record_list_updater.js.coffee
@@ -41,7 +41,7 @@
   # you have unselected items
   _removeEmptyMultiSelects: (query, form) =>
     form.find("select[multiple='multiple']").each ->
-      if (@.selectedOptions.length == 0)
+      if (@.selectedIndex == -1)
         delete query[@.name]
     query


Update visual

Update visual

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -59,6 +59,8 @@
               text: word.key
               displayText: word.label
               leftLabel: word.key
+              type: "constant"
+              iconHTML: '<i class="icon-move-right"></i>'
             }
             suggestions = suggestions.concat suggestion
           resolve(suggestions)


refactor: safe rendering (#185)

refactor: safe rendering (#185)

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -35,7 +35,8 @@
     task = Task.once taskPath, text, attributes

     task.on 'asciidoctor-render:success', ({html}) ->
-      resolve html
+      console.warn "Rendering is empty: #{filePath}" if not html
+      resolve html or ''

     task.on 'asciidoctor-render:error', ({code, errno, syscall, stack}) ->
       resolve """
@@ -52,6 +53,8 @@
         """

 sanitize = (html) ->
+  return html unless html
+
   o = cheerio.load(html)
   o('script').remove()
   attributesToRemove = [
@@ -82,6 +85,8 @@
   o.html()

 resolveImagePaths = (html, filePath) ->
+  return html unless html
+
   [rootDirectory] = atom.project.relativizePath(filePath)
   o = cheerio.load(html)
   for imgElement in o('img')


:lipstick:

:lipstick:

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -3,7 +3,7 @@

 module.exports =
   class Reporter
-    @sendEvent: (name, value)->
+    @sendEvent: (name, value) ->
       params =
         t: 'event'
         ec: 'session'
@@ -12,7 +12,7 @@

       @send(params)

-    @sendTiming: (name, value)->
+    @sendTiming: (name, value) ->
       params =
         t: 'timing'
         utc: 'app'


Simplify searchEngines default value.

Simplify searchEngines default value.

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -127,24 +127,23 @@
     # default/fall back search engine
     searchUrl: "https://www.google.com/search?q="
     # put in an example search engine
-    searchEngines: [
-      "w: http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s Wikipedia"
-      ""
-      "# More examples."
-      "#"
-      "# (Vimium has built-in completion for these.)"
-      "#"
-      "# g: http://www.google.com/search?q=%s Google"
-      "# l: http://www.google.com/search?q=%s&btnI I'm feeling lucky..."
-      "# y: http://www.youtube.com/results?search_query=%s Youtube"
-      "# b: https://www.bing.com/search?q=%s Bing"
-      "# d: https://duckduckgo.com/?q=%s DuckDuckGo"
-      "# az: http://www.amazon.com/s/?field-keywords=%s Amazon"
-      "#"
-      "# Another example (for Vimium does not have completion)."
-      "#"
-      "# m: https://www.google.com/maps/search/%s Google Maps"
-      ].join "\n"
+    searchEngines:
+      """
+      w: http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s Wikipedia
+
+      # More examples.
+      #
+      # (Vimium supports search completion Wikipedia, as
+      # above, and for these.)
+      #
+      # g: http://www.google.com/search?q=%s Google
+      # l: http://www.google.com/search?q=%s&btnI I'm feeling lucky...
+      # y: http://www.youtube.com/results?search_query=%s Youtube
+      # gm: https://www.google.com/maps?q=%s Google maps
+      # b: https://www.bing.com/search?q=%s Bing
+      # d: https://duckduckgo.com/?q=%s DuckDuckGo
+      # az: http://www.amazon.com/s/?field-keywords=%s Amazon
+      """
     newTabUrl: "chrome://newtab"
     grabBackFocus: false


experimentally infer and inject setting

experimentally infer and inject setting

Boolean without not description can directly set value no need to
{default: value}

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -1,5 +1,22 @@
+inferType = (value) ->
+  switch
+    when Number.isInteger(value) then 'integer'
+    when typeof(value) is 'boolean' then 'boolean'
+    when typeof(value) is 'string' then 'string'
+    when Array.isArray(value) then 'array'
+
 class Settings
   constructor: (@scope, @config) ->
+    # Inject order props to display orderd in setting-view
+    for name, i in Object.keys(@config)
+      @config[name].order = i
+
+    for key in Object.keys(@config)
+      if typeof(@config[key]) is 'boolean'
+        @config[key] = {default: @config[key]}
+      value = @config[key]
+      unless value.type?
+        value.type = inferType(value.default)

   get: (param) ->
     atom.config.get "#{@scope}.#{param}"
@@ -15,28 +32,11 @@

 module.exports = new Settings 'narrow',
   directionToOpen:
-    order: 0
-    type: 'string'
     default: 'right'
     enum: ['right', 'down', 'here']
     description: "Where to open"
-  vmpStartInInsertModeForUI:
-    order: 1
-    type: 'boolean'
-    default: true
-  LinesUseFuzzyFilter:
-    order: 2
-    type: 'boolean'
-    default: false
-  LinesKeepItemsOrderOnFuzzyFilter:
-    order: 3
-    type: 'boolean'
-    default: false
-  FoldUseFuzzyFilter:
-    order: 4
-    type: 'boolean'
-    default: false
-  FoldKeepItemsOrderOnFuzzyFilter:
-    order: 5
-    type: 'boolean'
-    default: false
+  vmpStartInInsertModeForUI: true
+  LinesUseFuzzyFilter: true
+  LinesKeepItemsOrderOnFuzzyFilter: false
+  FoldUseFuzzyFilter: false
+  FoldKeepItemsOrderOnFuzzyFilter: false


Fix support for zero-size viewBox

Fix support for zero-size viewBox

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -375,12 +375,10 @@
       else
         true

-    ## Set viewBox attribute if absent.
+    ## Compute viewBox attribute if absent.
     @viewBox = svgBBox @xml
-    if @viewBox? and not @xml.documentElement.hasAttribute 'viewBox'
-      @xml.documentElement.setAttribute 'viewBox', @viewBox.join ' '
-
-    # Overflow behavior
+
+    ## Overflow behavior
     overflow = attributeOrStyle @xml.documentElement, 'overflow'
     if @constructor.overflowDefault? and not overflow?
       @xml.documentElement.setAttribute 'overflow',
@@ -400,6 +398,9 @@
           @viewBox[2] = zeroSizeReplacement
         if @height == 0
           @viewBox[3] = zeroSizeReplacement
+    ## Reset viewBox attribute in case either absent (and computed via svgBBox)
+    ## or changed to avoid zeroes.
+    @xml.documentElement.setAttribute 'viewBox', @viewBox.join ' '
     @overflowBox = extractOverflowBox @xml
     if Symbol.forceWidth?
       @width = Symbol.forceWidth


Rename i to column

Rename i to column

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -94,12 +94,12 @@

   getPrefixText: (cursor) ->
     line = cursor.getCurrentBufferLine()
-    i = cursor.getBufferColumn() - 1
+    column = cursor.getBufferColumn() - 1
     prefix = []
-    while i >= 0
-      break if Snippet.prefixBoundary.test line[i]
-      prefix.unshift line[i]
-      i--
+    while column >= 0
+      break if Snippet.prefixBoundary.test line[column]
+      prefix.unshift line[column]
+      column--

     prefix.join ''


Dont return unintentional array

Dont return unintentional array

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -181,6 +181,7 @@
         else if not body?
           snippetsByPrefix[prefix] = null
       atom.config.set('snippets', snippetsByPrefix, source: filePath, scopeSelector: selector)
+    return

   getBodyParser: ->
     @bodyParser ?= require './snippet-body-parser'


Remove unused param

Remove unused param

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -8,7 +8,7 @@
       @div class: 'title', outlet: 'title'
       @div class: 'close-icon'

-  initialize: (@item, @pane) ->
+  initialize: (@item) ->
     @handleEvents()
     @updateDataAttributes()
     @updateTitle()


Set headers before sending response back to the client

--- lib/zenproxy.coffee
+++ lib/zenproxy.coffee
@@ -68,6 +68,7 @@
         method  : request.method

       proxy = http.request options, (res) ->
+        response.setHeader key, value for key, value of res.headers
         res.pipe response, end: true

       request.pipe proxy, end: true


fix listing

fix listing

--- static/index.coffee
+++ static/index.coffee
@@ -159,7 +159,7 @@
         return ''  unless filename?.length
         "#{date} #{time} #{filename}\n"
       filesHtml = filesHtml.join ''
-      $list.html(filesHtml).css 'display', ''
+      $list.html filesHtml

     fail = () ->

@@ -178,6 +178,7 @@
       list()
       return

+    $list.html ''
     if filename.indexOf('.') > 0
       [filename..., language] = filename.split '.'
       filename = filename.join '.'


style

style

--- static/index.coffee
+++ static/index.coffee
@@ -14,7 +14,7 @@
     evt.preventDefault()
     edit data
     $editor.off 'dblclick', startEditing
-    return false
+    false

   maybeSave = (evt) ->
     metaKey = evt.ctrlKey
@@ -63,7 +63,7 @@
       contentType: 'application/octet-stream'
       data
     }).always(always).done(done).fail(fail)
-    return false
+    false

   edit = (content = $editorCode[0].innerText) ->
     $linenosCode.html "#{metaKeyName}+s to Save - Shift+#{metaKeyName}+s to Save As...".replace /(.)/g, '$1<br>'


Removed some jQuery

Removed some jQuery

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -472,10 +472,11 @@
   _getInitialParams: ->
     thumbnails = if not @options.thumbnails then false else $(@thumbnails).height()

-    horizontal = toInt($(@overlay).css('padding-left')) + toInt($(@overlay).css('padding-right'))
-    vertical   = toInt($(@overlay).css('padding-top')) + toInt($(@overlay).css('padding-bottom'))
-
-    header = toInt $(@header).css('height')
+    css = getStyle @overlay
+    horizontal = toInt(css 'padding-left') + toInt(css 'padding-right')
+    vertical   = toInt(css 'padding-top') + toInt(css 'padding-bottom')
+
+    header = toInt getStyle(@header)('height')
     if header is 0
       header = 40
       $(@header).css 'height', header
@@ -496,7 +497,7 @@
   _outerWidth: (element) ->
     return element.outerWidth true if element.outerWidth?

-    css = getComputedStyle element
+    css = getStyle element

     styles = [
       'margin-left'
@@ -507,8 +508,8 @@
       'border-right-width'
     ]

-    width = parseFloat css.getPropertyValue 'width'
-    width += parseFloat css.getPropertyValue style for style in styles
+    width = parseFloat css 'width'
+    width += parseFloat css style for style in styles
     width

@@ -547,6 +548,10 @@
   if target.hasOwnProperty 'classList'
     target.classList.contains className

+getStyle = (element) ->
+  style = getComputedStyle element
+  return (property) -> style.getPropertyValue.call style, property
+

 toInt = (string) -> parseInt string, 10


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -19,7 +19,7 @@
   Keep an objects attributes within a given range.

-  # Player's health will be within [0, 100] at the end of
+  # Player's health will be within [0, 100] at the end of every update
   player.clamp
     health:
       min: 0


Using 1 line instead of 2 here

Using 1 line instead of 2 here

--- core/app/backbone/views/channels/auto_complete_channels_view.coffee
+++ core/app/backbone/views/channels/auto_complete_channels_view.coffee
@@ -13,8 +13,7 @@
       filter_on: 'slug_title'
       search_list_view: (options) -> new AutoCompleteSearchChannelsView(options)
       search_collection: -> new TopicSearchResults [], user: currentUser
-      placeholder: ->
-        Factlink.Global.t.topic_name.capitalize()
+      placeholder: -> Factlink.Global.t.topic_name.capitalize()

     @_results_view = new AutoCompleteResultsChannelsView(collection: @collection)


(- fix client closes:#3) runInNewContext wasn't calling into the DOM API properly.

`Node#insertBefore()` requires a second argument, that being *which element* to insert the element
before in the parent's NodeList. Most browsers were lenient and assumed the second argument to be
`null` if none is passed (which *also* meant we were unintentionally appending the element to the
*end* of the node-list, which is exactly the opposite of what we wanted.)

This mostly affected FireFox, it seems; everybody else was more lenient on requring the second
argument.

--- Paws/Source/utilities.coffee
+++ Paws/Source/utilities.coffee
@@ -21,24 +21,26 @@
          semaphore =
             source: source

+         parent = body(html(window.document))
          $frame = window.document.createElement 'iframe'
          $frame.style.display = 'none'
-         body(html(window.document)).insertBefore $frame
+         parent.insertBefore $frame, parent.firstChild
          $window = $frame.contentWindow
         #$window.document.close()

+         $parent = body(html($window.document))
          $window.__fromSemaphore = semaphore
          $script = $window.document.createElement 'script'
          $script.text = "window.__fromSemaphore.result = eval(window.__fromSemaphore.source)"
-         body(html($window.document)).insertBefore $script
+         $parent.insertBefore $script, $parent.firstChild

-         body(html(window.document)).removeChild $frame
+         parent.removeChild $frame

          return semaphore.result

       nodeFor = (type) -> (node) ->
          node.getElementsByTagName(type)[0] or
-         node.insertBefore (node.ownerDocument or node).createElement type
+         node.insertBefore (node.ownerDocument or node).createElement(type), node.firstChild

       html = nodeFor 'html'
       head = nodeFor 'head'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -220,7 +220,8 @@
           return Collision.rayRectangle source, direction,
             xw: target.width/2
             yw: target.height/2
-        els
+        else
+          ret

       xw = target.xw
       yw = target.yw


:lipstick: Reorder keys

:lipstick: Reorder keys

--- src/repository-status-handler.coffee
+++ src/repository-status-handler.coffee
@@ -18,8 +18,8 @@
     # Statuses in submodules
     for submodulePath, submoduleRepo of repo.submodules
       submodules[submodulePath] =
+        branch: submoduleRepo.getHead()
         upstream: submoduleRepo.getAheadBehindCount()
-        branch: submoduleRepo.getHead()

       workingDirectoryPath = submoduleRepo.getWorkingDirectory()
       for filePath, status of submoduleRepo.getStatus()


修正cola.util.dictionary最新出现的BUG

修正cola.util.dictionary最新出现的BUG

--- src/core/util.coffee
+++ src/core/util.coffee
@@ -203,34 +203,27 @@
 	if keyValues is null
 		delete keyValuesMap[name]
 		delete dictionaryMap[name]
+		return
 	else if keyValues is undefined
-		return keyValuesMap[name]?list
-	else if typeof keyValues is "array"
-		dictionaryMap[name] = {
-			list: keyValues
-		}
+		return keyValuesMap[name]
+	else if keyValues instanceof Array
+		keyValuesMap[name] = keyValues
+		dictionaryMap[name] = dictionary = {}
+		for pair in keyValues
+			dictionary[pair.key or ""] = pair.value
+		return dictionary
 	else
-		list = []
+		keyValuesMap[name] = values = []
 		for key, value of keyValues
-			list.push(
+			values.push(
 				key: key
 				value: value
 			)
-		dictionaryMap[name] = {
-			list: list
-			map: keyValues
-		}
-	return
+			dictionaryMap[name] = keyValues
+		return keyValues

 cola.util.translate = (dictionaryName, key) ->
-	holder = dictionaryMap[dictionaryName]
-	if holder
-		if not holder.map and holder.list
-			map = holder.map = {}
-			for pair in holder.list
-				map[pair.key or ""] = pair.value
-		return holder.map[key or ""]
-	return
+	return dictionaryMap[dictionaryName]?[key or ""]

 # OO


Fixed tab sizing in KDTabView. Used .toFixed instead of parseInt.

--- client/Framework/components/tabs/KDTabView.coffee
+++ client/Framework/components/tabs/KDTabView.coffee
@@ -277,8 +277,7 @@
       visibleHandles.push handle
       visibleTotalSize += handle.$().outerWidth no

-    sizeWhenUsedMaxHandleWidth = visibleHandles.length * options.maxHandleWidth;
-    possiblePercent = parseInt((100 - containerMarginInPercent) / visibleHandles.length, 10)
+    possiblePercent = ((100 - containerMarginInPercent) / visibleHandles.length).toFixed 2

     handle.setWidth(possiblePercent, "%") for handle in visibleHandles
   , 300


client: fix bug where hover on pm likes showed html tags as is

--- client/Social/Activity/views/replylikeview.coffee
+++ client/Social/Activity/views/replylikeview.coffee
@@ -60,7 +60,7 @@

     title = "#{names.join ''}#{andMore}"

-    @getTooltip().update {title}
+    @getTooltip().update {title, html:true}

   fetchAccounts: (callback) ->


Tests: Helpers: environmentHelpers: Changed helper selector.

--- client/test/lib/helpers/environmenthelpers.coffee
+++ client/test/lib/helpers/environmenthelpers.coffee
@@ -107,5 +107,5 @@
       .waitForElementVisible    '.env-modal.paid-plan', 25000
       .click                    '.env-modal.paid-plan button'
       .waitForElementNotVisible '.env-modal.paid-plan', 250000
-      .waitForElementVisible    'a[href="/IDE/koding-vm-1"]', 25000
+      .waitForElementVisible    'a[title="koding-vm-1"]', 25000
       .end()


Adding tabindex to labels so they're focusable.

--- src/glyphtree.coffee
+++ src/glyphtree.coffee
@@ -304,6 +304,7 @@
           .addClass(@_cr.node('icon'))
         $label = $('<span/>')
           .addClass(@_cr.node('label'))
+          .attr('tabindex', -1) # Allow focusing on label
           .text(@name)
         $li.append($icon)
         $li.append($label)


multiple-lines

multiple-lines

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -95,8 +95,10 @@
   robot.respond /dockerhub\s+trigger\s+show\s*$/, (res) ->
     robot.logger.debug("hubot-docker build-trigger show")
     triger_tokens = (robot.brain.get("dockerhub-trigger-tokens") || {})
+    msg = ""
     for repo, token of triger_tokens
-      res.send "#{repo} の build trigger のトークンは #{token} です"
+      msg += "#{repo} の build trigger のトークンは #{token} です\n"
+    res.send msg

   robot.respond /dockerhub\s+trigger\s+invoke\s+(\S+)\s*$/, (res) ->
     robot.logger.debug("hubot-docker build-trigger invoke")
@@ -115,10 +117,11 @@
           if post_err
             res.send "エラーが発生しました: #{post_err}"
           else
-            res.send post_res.statusCode
+            msg = "#{post_res.statusCode}\n"
             for k,v of post_res.headers
-              res.send "#{k}: #{v}"
-            res.send "#{post_body}"
+              msg += "#{k}: #{v}\n"
+            msg += "#{post_body}\n"
+            res.send msg
     else
       res.send "#{repo} のトークンが設定されていません"


advertise adapter

advertise adapter

--- src/dt-stream.coffee
+++ src/dt-stream.coffee
@@ -72,6 +72,8 @@
     constructor: (@template, opts = {}) ->
         super()
         @builder = @template.xml ? @template
+        @builder.apdaters ?= {}
+        @builder.apdaters['stream'] = this
         @autoremove = opts.autoremove ? on
         @encoding = opts.encoding ? 'utf8'
         @opened_tags = 0


Correct logic for stat result

Correct logic for stat result

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -172,7 +172,7 @@
         if err then cb err
         else
           exec "stat #{versionedPath}", (err, stdout, stderr)->
-            unless err or stderr.length
+            if err or stderr.length
               cb "[ERROR] Version is already published, change version and try again!"
             else
               exec "cp -r #{userAppPath} #{versionedPath}", (err, stdout, stderr)->
@@ -212,7 +212,7 @@
     #   else
     #     cb null
     exec "stat #{kpmAppPath}", (err, stdout, stderr)->
-      unless err or stderr.length
+      if err or stderr.length
         cb "[ERROR] App files not found! Download cancelled."
       else
         exec "mv #{userAppPath} #{backupPath} && cp -r #{kpmAppPath}/ #{userAppPath} && chown -R #{username}: #{userAppPath}", (err, stdout, stderr)->


upgrade the trampoline implementation

upgrade the trampoline implementation


change naming

change naming


Fixes in the upstreamer to include current method


Updated coloring to be more flexible.

Updated coloring to be more flexible.

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -1,6 +1,5 @@
 tty = require 'tty'
 fs = require 'fs'
-{EventEmitter} = require 'events'
 Core = require './core'

 module.exports =
@@ -65,8 +64,15 @@
       @write @encode(Array.prototype.slice.call(arguments))
     return @

-  color: (fore, back) ->
-    @set '[' + (if fore? and back? then (@codes.colors[fore].fore + ';' + @codes.colors[back].back) else 0) + 'm'
+  color: (data) ->
+    fore = (if data.fore? then data.fore else null)
+    back = (if data.back? then data.back else null)
+    if fore and back
+      @set '[' + @codes.colors[fore].fore + ';' + @codes.colors[back].back + 'm'
+    else if fore
+      @set '[' + @codes.colors[fore].fore + 'm'
+    else if back
+      @set '[' + @codes.colors[back].back + 'm'
     return @

   format: (code) ->
@@ -137,7 +143,7 @@

   prompt: (value) ->
     @pos @stdout.rows, 0
-    @color 'black', 'white'
+    @color {fore: 'black', back: 'white'}
     @fillLine()
     @pos @stdout.rows+1, 0
     @write '> ' + (if typeof value != 'undefined' then value else '')


Add intelmap -> Google Map translation

Add intelmap -> Google Map translation

--- src/intel-map.coffee
+++ src/intel-map.coffee
@@ -9,6 +9,7 @@
 #
 # Commands:
 #   hubot intelmap for <search>
+#   https://www.ingress.com/intel?ll=<lat>,<lng> - Return Google Maps image and link for intelmap location
 #

 module.exports = (robot) ->
@@ -41,6 +42,25 @@
     url = intelmapUrl result
     msg.send url

+  gmapImageUrl = (latlng) ->
+    return "https://maps.googleapis.com/maps/api/staticmap?center=" + latlng + "&size=400x300&zoom=15&markers=" + latlng
+
+  sendGmapImageLink = (msg, result) ->
+    url = gmapImageUrl result
+    msg.send url
+
+  gmapLink = (latlng) ->
+    return "https://maps.google.com/maps?ll=" + latlng + "&q=" + latlng
+
+  sendGmapLink = (msg, result) ->
+    url = gmapLink result
+    return msg.send url
+
   robot.respond /(intelmap|intel map)(?: for)?\s(.*)/i, (msg) ->
     location = msg.match[2]
     lookupLatLong msg, location, sendIntelLink
+
+  robot.hear /https:\/\/www.ingress.com\/intel\?ll=(-?\d+\.\d+,-?\d+\.\d+)/, (msg) ->
+    latlng = msg.match[1]
+    sendGmapImageLink msg, latlng
+    sendGmapLink msg, latlng


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -8,8 +8,7 @@

   self.bind "update", ->
     if I.fadeOut
-      log I.age
-      log I.duration
+      log I.alpha
       I.alpha = 1 - (I.age + 1 / I.duration)

     if I.duration != -1 && I.age + 1 >= I.duration


Don't call onOpenCb if undefined

Don't call onOpenCb if undefined

--- javascripts/WebsocketHandler.coffee
+++ javascripts/WebsocketHandler.coffee
@@ -18,7 +18,8 @@

     socket.onopen = ()->
       handleConnection()
-      onOpenCb()
+      if(onOpenCb)
+        onOpenCb()
     socket.onerror = handleError
     socket.onclose = handleClose
     socket.onmessage = handleMessage


Add some documentation, remove deprecated component.initialize call

--- src/lib/NoFlo.coffee
+++ src/lib/NoFlo.coffee
@@ -1,11 +1,21 @@
-# The main NoFlo runner
-
+#     NoFlo - Flow-Based Programming for Node.js
+#     (c) 2011 Henri Bergius, Nemein
+#     NoFlo may be freely distributed under the MIT license
 internalSocket = require "./InternalSocket"
 component = require "./Component"
 port = require "./Port"
 arrayport = require "./ArrayPort"
 graph = require "./Graph"

+# # The NoFlo network coordinator
+#
+# NoFlo networks consist of processes connected to each other
+# via sockets attached from outports to inports.
+#
+# The role of the network coordinator is to take a graph and
+# instantiate all the necessary processes from the designated
+# components, attach sockets between them, and handle the sending
+# of Initial Information Packets.
 class NoFlo
     processes: {}
     connections: []
@@ -17,21 +27,39 @@
         @connections = []
         @graph = graph

+        # As most NoFlo networks are long-running processes, the
+        # network coordinator marks down the start-up time. This
+        # way we can calculate the uptime of the network.
         @startupDate = new Date()

-        @graph.on "addNode", (node) =>
+        # A NoFlo graph may change after network initialization.
+        # For this, the network subscribes to the change events from
+        # the graph.
+        #
+        # In graph we talk about nodes and edges. Nodes correspond
+        # to NoFlo processes, and edges to connections between them.
+        @graph.on 'addNode', (node) =>
             @addNode node
-        @graph.on "removeNode", (node) =>
+        @graph.on 'removeNode', (node) =>
             @removeNode node
-        @graph.on "addEdge", (edge) =>
+        @graph.on 'addEdge', (edge) =>
             @addEdge edge
-        @graph.on "removeEdge", (edge) =>
+        @graph.on 'removeEdge', (edge) =>
             @removeEdge edge

+    # The uptime of the network is the current time minus the start-up
+    # time, in seconds.
     uptime: -> new Date() - @startupDate

+    # ## Loading components
+    #
+    # Components can be passed to the NoFlo network in two ways:
+    #
+    # * As direct, instantiated JavaScript objects
+    # * As filenames
     load: (component) ->
-        if typeof component is "object"
+        # Direct component instance, return as is
+        if typeof component is 'object'
             return component
         try
             if component.substr(0, 1) is "."
@@ -46,19 +74,27 @@
                 throw error
         implementation.getComponent()

+    # ## Add a process to the network
+    #
+    # Processes can be added to a network at either start-up time
+    # or later. The processes are added with a node definition object
+    # that includes the following properties:
+    #
+    # * `id`: Identifier of the process in the network. Typically a string
+    # * `component`: Filename or path of a NoFlo component, or a component instance object
     addNode: (node) ->
+        # Processes are treated as singletons by their identifier. If
+        # we already have a process with the given ID, return that.
         return if @processes[node.id]

-        process = {}
-
+        process =
+          id: node.id
+
+        # Load the process for the node.
         if node.component
             process.component = @load node.component

-        process.id = node.id
-
-        if node.config and process.component.initialize
-            process.component.initialize node.config
-
+        # Store and return the process instance
         @processes[process.id] = process

     removeNode: (node) ->
@@ -68,6 +104,7 @@

         delete @processes[node.id]

+    # Get process by its ID.
     getNode: (id) ->
         @processes[id]

@@ -139,7 +176,7 @@
     removeEdge: (edge) ->
         for connection,index in @connections
             if edge.to.node is connection.to.process.id and edge.to.port is connection.to.port
-                connection.to.process.component.inPorts[connection.to.port].detach connection
+                connection.to.process.component.inPorts[connection.to.port]?.detach connection
                 @connections.splice index, 1
             if edge.from.node
                 if connection.from and edge.from.node is connection.from.process.id and edge.from.port is connection.from.port


for not merged scripts.

for not merged scripts.

--- jenkins-job-selector-by-git-branch.coffee
+++ jenkins-job-selector-by-git-branch.coffee
@@ -126,10 +126,4 @@
         console.log "target_URL: #{job_url}" if debug

       else
-<<<<<<< HEAD
         console.log "#{prefix} unknown message type: " + typeof(msg) + "."
-
-
-=======
-        console.log "#{@prefix} unknown message type: " + typeof(msg) + "."
->>>>>>> development


engine mocking fixes

engine mocking fixes

--- js/test/search/MetadataMapper-test.coffee
+++ js/test/search/MetadataMapper-test.coffee
@@ -31,15 +31,15 @@

   describe 'MetadataMapper', ->

-    { kryptnosticEngine, metadataMapper, id, documentKey } = {}
+    { engine, metadataMapper, id, documentKey } = {}

     beforeEach ->
-      kryptnosticEngine = new MockKryptnosticEngine()
-      indexGenerator    = new StaticIndexGenerator()
-      metadataMapper    = new MetadataMapper()
-      _.extend(metadataMapper, { kryptnosticEngine, indexGenerator })
+      engine         = new MockKryptnosticEngine()
+      indexGenerator = new StaticIndexGenerator()
+      metadataMapper = new MetadataMapper()
+      _.extend(metadataMapper, { engine, indexGenerator })
       id          = 'some-object-id'
-      documentKey = kryptnosticEngine.getObjectSearchKey(id)
+      documentKey = engine.getObjectSearchKey(id)

     describe '#mapToKeys', ->


removed deprecated selector call

removed deprecated selector call

--- keymaps/regex-railroad-diagram.cson
+++ keymaps/regex-railroad-diagram.cson
@@ -7,5 +7,5 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
+'.atom-workspace':
   'ctrl-alt-o': 'regex-railroad-diagram:toggle'


Remove unnecessary `properties`.

Remove unnecessary `properties`.

--- src/nodes/doc.coffee
+++ src/nodes/doc.coffee
@@ -71,7 +71,6 @@
           line += lines.shift().substring(1)

       if property = /^@property\s+[\[\{](.+?)[\]\}](?:\s+(.+))?/i.exec line
-        @properties or= []
         @property = property[1]
         lines.push property[2]


try local import

try local import

--- lib/importers/geoname_nodeio.coffee
+++ lib/importers/geoname_nodeio.coffee
@@ -5,12 +5,12 @@

 module.exports = new nodeio.Job
   input: false
-  run: (f) ->
-    startJob new CityImport(), () =>
-      startJob new NamesImport(), () =>
-        @emit "geonames import finished."
+  run: (f) =>
+    #startJob new CityImport(), () =>
+    start new NamesImport(), () =>
+      @emit "geoname import finished."

-startJob = (job, callback) ->
+start = (job, callback) ->
   nodeio.start job, {}, (
     (err) ->
       if err
@@ -21,24 +21,40 @@
   ), false

-class AdminImport extends nodeio.JobClass
+class GeonameImport extends nodeio.JobClass
+  findBetterName: (geoname_id) ->
+    name = "foo" # as we want the best available name as key
+    redis.smembers "alt:geoname:#{place[0]}", (err, alts) ->
+      prefered = a for a in alts when a.match(/.*:1:.*/)
+      console.log prefered.length
+      console.log p for p in prefered
+      if prefered.length > 1
+        console.log "shorts:"
+        short = a for a in alt when a.match(/^.*:1:1:.*/)
+        console.log p for p in short
+    name
+  store: (primary_key, foreign_key) ->
+    console.log "sure"
+
+class AdminImport extends GeonameImport
   input: "/tmp/#{COUNTRY}.txt"
   description: "import of administrative divisions in #{COUNTRY}"
   run: (row) =>
     place = @parseValues(row,'\t')
     if place[7] && place[7].match /ADM1/
-      foreign_key = "geonames:id:#{place[0]}"   # geoname:id:42
-      primary_key = "#{place[8]}:#{place[1]}"   # DE:Thueringen
+      name = @findBetterName(place[0])
+      primary_key = "#{place[8]}:#{name}"   # DE:Thueringen
+      foreign_key = "geoname:id:#{place[0]}"   # geoname:id:42
       redis.sadd primary_key, foreign_key
       redis.set foreign_key, primary_key
-      console.log " + stored admin div: "+place[1]
-    @exit
+      console.log " + stored admin: "+place[1]
+      @emit true
   output: false

 adminCodes = {}

-class CityImport extends nodeio.JobClass
+class CityImport extends GeonameImport
   input: "/tmp/#{COUNTRY}.txt"
   description: "import of cities in #{COUNTRY}"
   constructor: ->
@@ -49,46 +65,30 @@
         adminCodes[line[0].split(".")[1]] = line[3]
   run: (row) =>
     place = @parseValues(row,'\t')
-    #population = data[14]
-    #lon = data[4]
-    #lat = data[5]
+    #population = data[14]          #lon = data[4]        #lat = data[5]
     if place[7] && place[7].match /PPL.*/
-      redis.get "geonames:id:#{adminCodes[place[10]]}", (err, admin_key) ->
+      redis.get "geoname:id:#{adminCodes[place[10]]}", (err, admin_key) =>
         if admin_key && admin_key.substring(0,2) == place[8] # consistency
-          foreign_key = "geonames:id:#{place[0]}"     # geoname:id:1234567
-          primary_key = "#{admin_key}:#{place[1]}"    # DE:Bavaria:München
+          name = @findBetterName(place[0])
+          primary_key = "#{admin_key}:#{name}"    # DE:Bavaria:München
+          foreign_key = "geoname:id:#{place[0]}"     # geoname:id:1234567
           redis.sadd primary_key, foreign_key
           redis.set foreign_key, primary_key
           #console.log " + stored city: "+place[1]
         else
-          console.log " - something Kompost here with "+place[1] +
+          console.log " - something Kompost in here: "+place[1] +
             "! adminCode is "+place[10]+", Key is "+admin_key
     @exit
   output: false

 class NamesImport extends nodeio.JobClass
   input: "/tmp/alternateNames.txt"
-  description: "import of alternative names in #{COUNTRY}"
+  description: "tmp import of alternative geonames"
   run: (row) =>
     data = @parseValues(row,'\t')
-    foreign_key = "geonames:id:#{data[1]}"
-    redis.get foreign_key, (err, internal_key) =>
-      if internal_key && (data[2] == "de" || data[2] == "") && data[4] == "1"  # prefered name
-        better_internal_key = internal_key.match(/(.*:)[^:]+$/)[1]+data[3]
-        redis.smembers internal_key, (err, foreign_keys) =>
-          console.log "To update: "+foreign_keys.length
-          redis.multi(["set", key, better_internal_key] for key in foreign_keys).exec()
-          redis.multi(["sadd", better_internal_key, key] for key in foreign_keys)
-            .exec (err, success) =>
-              redis.del internal_key, (err, success) =>
-                console.log " -/+ replaced internal key: "+internal_key +
-                  " with a better internal key "+better_internal_key
-                @emit true
-      else if internal_key && data[2] != ""
-        redis.set "geonames:alt:#{data[3]}", internal_key
-        redis.sadd internal_key, "geonames:alt:#{data[3]}"
-        console.log " + stored alt name: "+data[3] +
-          " for internal key " + internal_key
+    redis.sadd "alt:geoname:#{data[0]}",
+      "#{data[2]}:#{data[4]}:#{data[5]}:#{data[3]}",
+      (error, success) =>
         @emit true
   output: false


Update keymap method

Update keymap method

--- lib/keybinding-resolver-view.coffee
+++ lib/keybinding-resolver-view.coffee
@@ -22,6 +22,7 @@
   destroy: ->
     @detach()

+
   toggle: ->
     if @hasParent()
       @detach()
@@ -34,7 +35,7 @@
   handleEvent: (event) ->
     keystroke = atom.keymap.keystrokeStringForEvent(event)
     mappings = atom.keymap.mappingsForKeystroke(keystroke)
-    matchedMappings = atom.keymap.mappingsMatchingElement(mappings, document.activeElement)
+    matchedMappings = atom.keymap.mappingsMatchingElement(document.activeElement, mappings)
     unmatchedMappings = mappings.filter (mapping) ->
       for matchedMapping in matchedMappings
         return false if _.isEqual(matchedMapping, mapping)


cleaning up subdivide with 4.times

cleaning up subdivide with 4.times

--- src/quad_tree.coffee
+++ src/quad_tree.coffee
@@ -106,11 +106,13 @@

         x = I.bounds.x
         y = I.bounds.y
+
         width = I.bounds.width
         height = I.bounds.height

         half_width = (width / 2).floor()
         half_height = (height / 2).floor()
+
         x_midpoint = x + half_width
         y_midpoint = y + half_height


extra comment for RatchetBox

extra comment for RatchetBox

--- src/rachetbox.coffee
+++ src/rachetbox.coffee
@@ -8,6 +8,16 @@
 KeyRatchet  = require 'keyratchet'
 Mailbox     = require 'mailbox'

+# RatchetBox is a prototype to show a simple ratcheting schema implementation via Glow.
+# By overload relay_send and relay_messages we can introduce a key ratchet between
+# mailbox and its guests. All payload messages (object trees) are delivered along with
+# system field 'next_key' that contains next public key in the ratchet. The guest
+# confirm key with system field 'got_key'. Any message to guest or from guest advances
+# the ratchet to next_key from that side.
+#
+# TODO: provision for loss of messages by introducing 'reset' field - mailboxes
+# can singnal they dont have key material to decrypt laste message. That will revert
+# ratchet to long term idenitiy key and will cause the start of new key chain.
 class RatchetBox extends Mailbox

   _loadRatchets: (guest) ->


update api and readme for 0.0.13

update api and readme for 0.0.13

--- test/register-coffee-coverage.coffee
+++ test/register-coffee-coverage.coffee
@@ -2,6 +2,15 @@
 require('coffee-coverage').register {
   path: 'relative'
   basePath: require('path').join __dirname, '..'
-  exclude: ['test', 'lib', 'dist', 'bin', 'node_modules', '.git', 'Gulpfile']
+  exclude: [
+    'test'
+    'lib'
+    'dist'
+    'bin'
+    'node_modules'
+    '.git'
+    'Gulpfile'
+    'examples'
+  ]
   initAll: true
 }


Open survey in self to avoid pop-up blockers

--- src/load/list.coffee
+++ src/load/list.coffee
@@ -2,7 +2,7 @@
 {make} = require '../render/make'

 exports.list = (root, games, filter) ->
-  window.open my.feedback_url if my.beta unless filter == "test"
+  window.open my.feedback_url, '_self' if my.beta unless filter == "test"

   list = root.add_child {
     name: 'Game Listings'


Fix specs in buffer search spec

Fix specs in buffer search spec

--- spec/buffer-search-spec.coffee
+++ spec/buffer-search-spec.coffee
@@ -1,5 +1,6 @@
 {TextEditor} = require 'atom'
 BufferSearch = require '../lib/buffer-search'
+FindOptions = require '../lib/find-options'

 describe "BufferSearch", ->
   [search, editor, markersListener, currentResultListener] = []
@@ -20,7 +21,8 @@
       -----------
     """

-    search = new BufferSearch
+    findOptions = new FindOptions
+    search = new BufferSearch(findOptions)

     markersListener = jasmine.createSpy('markersListener')
     search.onDidUpdate(markersListener)
@@ -31,8 +33,8 @@
     search.setEditor(editor)
     markersListener.reset()

-    search.setSearchParams(
-      pattern: "a+"
+    search.setFindOptions(
+      findPattern: "a+"
       caseSensitive: false
       useRegex: true
       wholeWord: false


Slight change in has_HMD() rejection message.

--- engine/webvr.coffee
+++ engine/webvr.coffee
@@ -16,7 +16,7 @@
                     HMD = display
                     break
             if not HMD?
-                return reject "No HMDs detected. Conect an HMD, turn it on and try again."
+                return reject "No HMDs detected."
             resolve true

 exports.init = (scene, options={}) ->


Add update functionality

Add update functionality


Fix password symbol generation.

Fix password symbol generation.

--- src/passwdgen.coffee
+++ src/passwdgen.coffee
@@ -2,19 +2,14 @@

 config =
 	pw: { min_size: 8, max_size: 16 }
-
-is_upper = (c) -> "A".charCodeAt(0) <= c and c <= "Z".charCodeAt(0)
-is_lower = (c) -> "a".charCodeAt(0) <= c and c <= "z".charCodeAt(0)
-is_digit = (c) -> "0".charCodeAt(0) <= c and c <= "9".charCodeAt(0)
-is_valid = (c) -> @is_upper(c) or @is_lower(c) or @is_digit (c)

 class PasswdGenerator
 	constructor: (@email, passphrase, itercnt) ->
 		# TODO If localStorage has stored key, no need to generate again
 		@_key = @derive(@email, passphrase)

-	# Private function
 	derive: (email, passphrase, itercnt) ->
+		# TODO check if the derived key is the same as 1SP
 		C.PBKDF2 passphrase, email,
 			{ keySize: 512/32, iterations: itercnt, hasher: C.algo.SHA512 }

@@ -24,17 +19,15 @@
 		i = 0
 		ret = null
 		tmpl = [ "OneShallPass v2.0", @email, passwd.site, passwd.generation ].join ""
-		k = C.HmacSHA512 tmpl.concat("0"), @_key
-		k = k.toString C.enc.Base64
-		k[0...passwd.length]
-		# until ret
-		# 	a = tmpl.concat(i.toString())
-		# 	hash = C.HmacSHA512 a, @_key
-		# 	b64 = hash.toString C.enc.Base64
-		# 	ret = b64 if @is_ok_pw b64, passwd
-		# 	i++
-		# x = @add_syms ret, passwd.num_symbols
-		# x[0...passwd.length]
+		until ret
+			# TODO 1SP uses purepack to concatenate strings
+			a = tmpl.concat i.toString()
+			hash = C.HmacSHA512 a, @_key
+			b64 = hash.toString C.enc.Base64
+			ret = b64 if @is_ok_pw b64
+			i++
+		x = @add_syms ret, passwd.num_symbols
+		x[0...passwd.length]

 	# Rules for 'OK' passwords:
 	#    - Within the first 8 characters:
@@ -43,23 +36,23 @@
 	#       - No symbols
 	#    - From characters 7 to 16:
 	#       - No symbols
-	is_ok_pw: (pw, passwd) ->
+	is_ok_pw: (pw) ->
 		caps = 0
 		lowers = 0
 		digits = 0

 		for i in [0...config.pw.min_size]
 			c = pw.charCodeAt i
-			if is_digit c then digits++
-			else if is_upper c then caps++
-			else if is_lower c then lowers++
+			if @is_digit c then digits++
+			else if @is_upper c then caps++
+			else if @is_lower c then lowers++
 			else return false

-			bad = (x) -> (x is 0 or x > 5)
-			return false if bad(digits) or bad(lowers) or bad(caps)
+		bad = (x) -> (x is 0 or x > 5)
+		return false if bad(digits) or bad(lowers) or bad(caps)

-			for i in [config.pw.min_size...config.pw.max_size]
-				return false unless @is_valid pw.charCodeAt i
+		for i in [config.pw.min_size...config.pw.max_size]
+			return false unless @is_valid pw.charCodeAt i

 		true

@@ -80,9 +73,9 @@
 			else if @is_upper c then caps++
 			else if @is_lower c then lowers++

-		if lowers >= caps and lowers >= digits then is_lower
-		else if digits > lowers and digits >= caps then is_digit
-		else is_upper
+		if lowers >= caps and lowers >= digits then @is_lower
+		else if digits > lowers and digits >= caps then @is_digit
+		else @is_upper

 	add_syms: (input, n) ->
 		return input if n <= 0
@@ -113,4 +106,9 @@
 		arr.push input[last...]
 		arr.join ""

+	is_upper: (c) -> "A".charCodeAt(0) <= c and c <= "Z".charCodeAt(0)
+	is_lower: (c) -> "a".charCodeAt(0) <= c and c <= "z".charCodeAt(0)
+	is_digit: (c) -> "0".charCodeAt(0) <= c and c <= "9".charCodeAt(0)
+	is_valid: (c) -> @is_upper(c) or @is_lower(c) or @is_digit (c)
+
 window.PasswdGenerator = PasswdGenerator


Roll two whens into one

Roll two whens into one

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -456,9 +456,7 @@
           atom.notifications.addWarning("#{error.message} Large file support is being tracked at [atom/atom#307](https://github.com/atom/atom/issues/307).")
         when 'EACCES'
           atom.notifications.addWarning("Permission denied '#{error.path}'")
-        when 'EPERM'
-          atom.notifications.addWarning("Unable to open '#{error.path}'", detail: error.message)
-        when 'EBUSY'
+        when 'EPERM', 'EBUSY'
           atom.notifications.addWarning("Unable to open '#{error.path}'", detail: error.message)
         else
           throw error


[misc] test/acceptance: ReceiveUpdateTests: add 2nd project/3rd client

...and check for cross project leakage.

--- services/real-time/test/acceptance/coffee/ReceiveUpdateTests.coffee
+++ services/real-time/test/acceptance/coffee/ReceiveUpdateTests.coffee
@@ -13,7 +13,7 @@
 rclient = redis.createClient(settings.redis.pubsub)

 describe "receiveUpdate", ->
-	before (done) ->
+	beforeEach (done) ->
 		@lines = ["test", "doc", "lines"]
 		@version = 42
 		@ops = ["mock", "doc", "ops"]
@@ -52,15 +52,52 @@

 			(cb) =>
 				@clientB.emit "joinDoc", @doc_id, cb
+
+			(cb) =>
+				FixturesManager.setUpProject {
+					privilegeLevel: "owner"
+					project: {name: "Test Project"}
+				}, (error, {user_id: @user_id_second, project_id: @project_id_second}) => cb()
+
+			(cb) =>
+				FixturesManager.setUpDoc @project_id_second, {@lines, @version, @ops}, (e, {doc_id: @doc_id_second}) =>
+					cb(e)
+
+			(cb) =>
+				@clientC = RealTimeClient.connect()
+				@clientC.on "connectionAccepted", cb
+
+			(cb) =>
+				@clientC.emit "joinProject", {
+					project_id: @project_id_second
+				}, cb
+			(cb) =>
+				@clientC.emit "joinDoc", @doc_id_second, cb
+
+			(cb) =>
+				@clientAUpdates = []
+				@clientA.on "otUpdateApplied", (update) => @clientAUpdates.push(update)
+				@clientBUpdates = []
+				@clientB.on "otUpdateApplied", (update) => @clientBUpdates.push(update)
+				@clientCUpdates = []
+				@clientC.on "otUpdateApplied", (update) => @clientCUpdates.push(update)
+
+				@clientAErrors = []
+				@clientA.on "otUpdateError", (error) => @clientAErrors.push(error)
+				@clientBErrors = []
+				@clientB.on "otUpdateError", (error) => @clientBErrors.push(error)
+				@clientCErrors = []
+				@clientC.on "otUpdateError", (error) => @clientCErrors.push(error)
+				cb()
 		], done
-
+
+	afterEach () ->
+		@clientA?.disconnect()
+		@clientB?.disconnect()
+		@clientC?.disconnect()
+
 	describe "with an update from clientA", ->
-		before (done) ->
-			@clientAUpdates = []
-			@clientA.on "otUpdateApplied", (update) => @clientAUpdates.push(update)
-			@clientBUpdates = []
-			@clientB.on "otUpdateApplied", (update) => @clientBUpdates.push(update)
-
+		beforeEach (done) ->
 			@update = {
 				doc_id: @doc_id
 				op:
@@ -80,21 +117,69 @@
 			@clientAUpdates.should.deep.equal [{
 				v: @version, doc: @doc_id
 			}]
-
-	describe "with an error", ->
-		before (done) ->
-			@clientAErrors = []
-			@clientA.on "otUpdateError", (error) => @clientAErrors.push(error)
-			@clientBErrors = []
-			@clientB.on "otUpdateError", (error) => @clientBErrors.push(error)
-
+
+		it "should send nothing to clientC", ->
+			@clientCUpdates.should.deep.equal []
+
+	describe "with an update from clientC", ->
+		beforeEach (done) ->
+			@update = {
+				doc_id: @doc_id_second
+				op:
+					meta:
+						source: @clientC.publicId
+					v: @version
+					doc: @doc_id_second
+					op: [{i: "update from clientC", p: 50}]
+			}
+			rclient.publish "applied-ops", JSON.stringify(@update)
+			setTimeout done, 200 # Give clients time to get message
+
+		it "should send nothing to clientA", ->
+			@clientAUpdates.should.deep.equal []
+
+		it "should send nothing to clientB", ->
+			@clientBUpdates.should.deep.equal []
+
+		it "should send an ack to clientC", ->
+			@clientCUpdates.should.deep.equal [{
+				v: @version, doc: @doc_id_second
+			}]
+
+	describe "with an error for the first project", ->
+		beforeEach (done) ->
 			rclient.publish "applied-ops", JSON.stringify({doc_id: @doc_id, error: @error = "something went wrong"})
 			setTimeout done, 200 # Give clients time to get message
-
-		it "should send the error to both clients", ->
+
+		it "should send the error to the clients in the first project", ->
 			@clientAErrors.should.deep.equal [@error]
 			@clientBErrors.should.deep.equal [@error]
-
-		it "should disconnect the clients", ->
+
+		it "should not send any errors to the client in the second project", ->
+			@clientCErrors.should.deep.equal []
+
+		it "should disconnect the clients of the first project", ->
 			@clientA.socket.connected.should.equal false
 			@clientB.socket.connected.should.equal false
+
+		it "should not disconnect the client in the second project", ->
+			@clientC.socket.connected.should.equal true
+
+	describe "with an error for the second project", ->
+		beforeEach (done) ->
+			rclient.publish "applied-ops", JSON.stringify({doc_id: @doc_id_second, error: @error = "something went wrong"})
+			setTimeout done, 200 # Give clients time to get message
+
+		it "should not send any errors to the clients in the first project", ->
+			@clientAErrors.should.deep.equal []
+			@clientBErrors.should.deep.equal []
+
+		it "should send the error to the client in the second project", ->
+			@clientCErrors.should.deep.equal [@error]
+
+		it "should not disconnect the clients of the first project", ->
+			@clientA.socket.connected.should.equal true
+			@clientB.socket.connected.should.equal true
+
+		it "should disconnect the client in the second project", ->
+			@clientC.socket.connected.should.equal false


Add Session#disconnect and Session#connected methods.

--- web/lib/coffeescripts/session.coffee
+++ web/lib/coffeescripts/session.coffee
@@ -22,6 +22,8 @@
             this.findCards()
           @xmpp.send $pres().tree()

+  disconnect: -> @xmpp.disconnect()
+
   onCard: (callback) ->
     @listeners['card'].push callback

@@ -33,6 +35,9 @@

   onPresence: (callback) ->
     @listeners['presence'].push callback
+
+  connected: ->
+    @xmpp.jid && @xmpp.jid.length > 0

   jid: -> @xmpp.jid


Reformat Selection comments

Reformat Selection comments

--- src/selection.coffee
+++ src/selection.coffee
@@ -14,8 +14,8 @@
   wordwise: false
   needsAutoscroll: null

-  ### Internal ###
-
+
+  # Private:
   constructor: ({@cursor, @marker, @editSession}) ->
     @cursor.selection = this
     @marker.on 'changed', => @screenRangeChanged()
@@ -24,65 +24,62 @@
       @editSession.removeSelection(this)
       @trigger 'destroyed' unless @editSession.destroyed

+  # Private:
   destroy: ->
     @marker.destroy()

+  # Private:
   finalize: ->
     @initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
     if @isEmpty()
       @wordwise = false
       @linewise = false

+  # Private:
   clearAutoscroll: ->
     @needsAutoscroll = null

-  ### Public ###
-
-  # Identifies if the selection is highlighting anything.
-  #
-  # Returns a {Boolean}.
+  # Public: Determines if the selection contains anything.
   isEmpty: ->
     @getBufferRange().isEmpty()

-  # Identifies if the ending position of a marker is greater than the starting position.
+  # Public: Determines if the ending position of a marker is greater than the
+  # starting position.
   #
   # This can happen when, for example, you highlight text "up" in a {TextBuffer}.
-  #
-  # Returns a {Boolean}.
   isReversed: ->
     @marker.isReversed()

-  # Identifies if the selection is a single line.
-  #
-  # Returns a {Boolean}.
+  # Public: Returns whether the selection is a single line or not.
   isSingleScreenLine: ->
     @getScreenRange().isSingleLine()

-  # Retrieves the screen range for the selection.
-  #
-  # Returns a {Range}.
+  # Public: Returns the screen {Range} for the selection.
   getScreenRange: ->
     @marker.getScreenRange()

-  # Modifies the screen range for the selection.
-  #
-  # screenRange - The new {Range} to use
-  # options - A hash of options matching those found in {.setBufferRange}
+  # Public: Modifies the screen range for the selection.
+  #
+  # * screenRange:
+  #   The new {Range} to use
+  # * options:
+  #   + A hash of options matching those found in {.setBufferRange}
   setScreenRange: (screenRange, options) ->
     @setBufferRange(@editSession.bufferRangeForScreenRange(screenRange), options)

-  # Retrieves the buffer range for the selection.
-  #
-  # Returns a {Range}.
+  # Public: Returns the buffer {Range} for the selection.
   getBufferRange: ->
     @marker.getBufferRange()

-  # Modifies the buffer range for the selection.
-  #
-  # screenRange - The new {Range} to select
-  # options - A hash of options with the following keys:
-  #           preserveFolds: if `true`, the fold settings are preserved after the selection moves
-  #           autoscroll: if `true`, the {EditSession} scrolls to the new selection
+  # Public: Modifies the buffer {Range} for the selection.
+  #
+  # * screenRange:
+  #   The new {Range} to select
+  # * options
+  #    + preserveFolds:
+  #      if `true`, the fold settings are preserved after the selection moves
+  #    + autoscroll:
+  #      if `true`, the {EditSession} scrolls to the new selection
   setBufferRange: (bufferRange, options={}) ->
     bufferRange = Range.fromObject(bufferRange)
     @needsAutoscroll = options.autoscroll
@@ -92,7 +89,8 @@
       @cursor.needsAutoscroll = false if options.autoscroll?
       @marker.setBufferRange(bufferRange, options)

-  # Retrieves the starting and ending buffer rows the selection is highlighting.
+  # Public: Returns the starting and ending buffer rows the selection is
+  # highlighting.
   #
   # Returns an {Array} of two {Number}s: the starting row, and the ending row.
   getBufferRowRange: ->
@@ -102,18 +100,16 @@
     end = Math.max(start, end - 1) if range.end.column == 0
     [start, end]

-  # Retrieves the text in the selection.
-  #
-  # Returns a {String}.
+  # Public: Returns the text in the selection.
   getText: ->
     @editSession.buffer.getTextInRange(@getBufferRange())

-  # Clears the selection, moving the marker to move to the head.
+  # Public: Clears the selection, moving the marker to the head.
   clear: ->
     @marker.setAttributes(goalBufferRange: null)
     @marker.clearTail() unless @retainSelection

-  # Modifies the selection to mark the current word.
+  # Public: Modifies the selection to encompass the current word.
   #
   # Returns a {Range}.
   selectWord: ->
@@ -124,12 +120,16 @@
     @wordwise = true
     @initialScreenRange = @getScreenRange()

+  # Public:
+  #
+  # FIXME: I have no idea what this does.
   expandOverWord: ->
     @setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))

-  # Selects an entire line in the {TextBuffer}.
-  #
-  # row - The line {Number} to select (default: the row of the cursor)
+  # Public: Selects an entire line in the buffer.
+  #
+  # * row:
+  #   The line Number to select (default: the row of the cursor)
   selectLine: (row=@cursor.getBufferPosition().row) ->
     range = @editSession.bufferRangeForBufferRow(row, includeNewline: true)
     @setBufferRange(range)
@@ -137,13 +137,18 @@
     @wordwise = false
     @initialScreenRange = @getScreenRange()

+  # Public:
+  #
+  # FIXME: I have no idea what this does.
   expandOverLine: ->
     range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
     @setBufferRange(range)

-  # Selects the text from the current cursor position to a given screen position.
-  #
-  # position - An instance of {Point}, with a given `row` and `column`.
+  # Public: Selects the text from the current cursor position to a given screen
+  # position.
+  #
+  # * position:
+  #   An instance of {Point}, with a given `row` and `column`.
   selectToScreenPosition: (position) ->
     @modifySelection =>
       if @initialScreenRange
@@ -159,73 +164,83 @@
       else if @wordwise
         @expandOverWord()

-  # Selects the text from the current cursor position to a given buffer position.
-  #
-  # position - An instance of {Point}, with a given `row` and `column`.
+  # Public: Selects the text from the current cursor position to a given buffer
+  # position.
+  #
+  # * position:
+  #   An instance of {Point}, with a given `row` and `column`.
   selectToBufferPosition: (position) ->
     @modifySelection => @cursor.setBufferPosition(position)

-  # Selects the text one position right of the cursor.
+  # Public: Selects the text one position right of the cursor.
   selectRight: ->
     @modifySelection => @cursor.moveRight()

-  # Selects the text one position left of the cursor.
+  # Public: Selects the text one position left of the cursor.
   selectLeft: ->
     @modifySelection => @cursor.moveLeft()

-  # Selects all the text one position above the cursor.
+  # Public: Selects all the text one position above the cursor.
   selectUp: ->
     @modifySelection => @cursor.moveUp()

-  # Selects all the text one position below the cursor.
+  # Public: Selects all the text one position below the cursor.
   selectDown: ->
     @modifySelection => @cursor.moveDown()

-  # Selects all the text from the current cursor position to the top of the buffer.
+  # Public: Selects all the text from the current cursor position to the top of
+  # the buffer.
   selectToTop: ->
     @modifySelection => @cursor.moveToTop()

-  # Selects all the text from the current cursor position to the bottom of the buffer.
+  # Public: Selects all the text from the current cursor position to the bottom
+  # of the buffer.
   selectToBottom: ->
     @modifySelection => @cursor.moveToBottom()

-  # Selects all the text in the buffer.
+  # Public: Selects all the text in the buffer.
   selectAll: ->
     @setBufferRange(@editSession.buffer.getRange(), autoscroll: false)

-  # Selects all the text from the current cursor position to the beginning of the line.
+  # Public: Selects all the text from the current cursor position to the
+  # beginning of the line.
   selectToBeginningOfLine: ->
     @modifySelection => @cursor.moveToBeginningOfLine()

-  # Selects all the text from the current cursor position to the first character of the line.
+  # Public: Selects all the text from the current cursor position to the first
+  # character of the line.
   selectToFirstCharacterOfLine: ->
     @modifySelection => @cursor.moveToFirstCharacterOfLine()

-  # Selects all the text from the current cursor position to the end of the line.
+  # Public: Selects all the text from the current cursor position to the end of
+  # the line.
   selectToEndOfLine: ->
     @modifySelection => @cursor.moveToEndOfLine()

-  # Selects all the text from the current cursor position to the beginning of the word.
+  # Public: Selects all the text from the current cursor position to the
+  # beginning of the word.
   selectToBeginningOfWord: ->
     @modifySelection => @cursor.moveToBeginningOfWord()

-  # Selects all the text from the current cursor position to the end of the word.
+  # Public: Selects all the text from the current cursor position to the end of
+  # the word.
   selectToEndOfWord: ->
     @modifySelection => @cursor.moveToEndOfWord()

-  # Selects all the text from the current cursor position to the beginning of the next word.
+  # Public: Selects all the text from the current cursor position to the
+  # beginning of the next word.
   selectToBeginningOfNextWord: ->
     @modifySelection => @cursor.moveToBeginningOfNextWord()

-  # Selects text to the previous word boundary.
+  # Public: Selects text to the previous word boundary.
   selectToPreviousWordBoundary: ->
     @modifySelection => @cursor.moveToPreviousWordBoundary()

-  # Selects text to the next word boundary.
+  # Public: Selects text to the next word boundary.
   selectToNextWordBoundary: ->
     @modifySelection => @cursor.moveToNextWordBoundary()

-  # Moves the selection down one row.
+  # Public: Moves the selection down one row.
   addSelectionBelow: ->
     range = (@getGoalBufferRange() ? @getBufferRange()).copy()
     nextRow = range.end.row + 1
@@ -243,10 +258,13 @@
       @editSession.addSelectionForBufferRange(range, goalBufferRange: range)
       break

+  # Public:
+  #
+  # FIXME: I have no idea what this does.
   getGoalBufferRange: ->
     @marker.getAttributes().goalBufferRange

-  # Moves the selection up one row.
+  # Public: Moves the selection up one row.
   addSelectionAbove: ->
     range = (@getGoalBufferRange() ? @getBufferRange()).copy()
     previousRow = range.end.row - 1
@@ -264,15 +282,20 @@
       @editSession.addSelectionForBufferRange(range, goalBufferRange: range)
       break

-  # Replaces text at the current selection.
-  #
-  # text - A {String} representing the text to add
-  # options - A hash containing the following options:
-  #           select: if `true`, selects the newly added text
-  #           autoIndent: if `true`, indents all inserted text appropriately
-  #           autoIndentNewline: if `true`, indent newline appropriately
-  #           autoDecreaseIndent: if `true`, decreases indent level appropriately (for example, when a closing bracket is inserted)
-
+  # Public: Replaces text at the current selection.
+  #
+  # * text:
+  #   A {String} representing the text to add
+  # * options
+  #    + select:
+  #      if `true`, selects the newly added text
+  #    + autoIndent:
+  #      if `true`, indents all inserted text appropriately
+  #    + autoIndentNewline:
+  #      if `true`, indent newline appropriately
+  #    + autoDecreaseIndent:
+  #      if `true`, decreases indent level appropriately (for example, when a
+  #      closing bracket is inserted)
   insertText: (text, options={}) ->
     oldBufferRange = @getBufferRange()
     @editSession.destroyFoldsContainingBufferRow(oldBufferRange.end.row)
@@ -298,6 +321,12 @@

     newBufferRange

+  # Public: Indents the given text to the suggested level based on the grammar.
+  #
+  # * text:
+  #   The string to indent within the selection.
+  # * indentBasis:
+  #   The beginning indent level.
   normalizeIndents: (text, indentBasis) ->
     textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
     isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
@@ -323,10 +352,12 @@

     normalizedLines.join('\n')

-  # Indents the selection.
-  #
-  # options - A hash with one key, `autoIndent`. If `true`, the indentation is
-  #           performed appropriately. Otherwise, {EditSession.getTabText} is used
+  # Public: Indents the selection.
+  #
+  # * options - A hash with one key,
+  #    + autoIndent:
+  #      If `true`, the indentation is performed appropriately. Otherwise,
+  #      {EditSession.getTabText} is used
   indent: ({ autoIndent }={})->
     { row, column } = @cursor.getBufferPosition()

@@ -342,28 +373,32 @@
     else
       @indentSelectedRows()

-  # If the selection spans multiple rows, indents all of them.
+  # Public: If the selection spans multiple rows, indent all of them.
   indentSelectedRows: ->
     [start, end] = @getBufferRowRange()
     for row in [start..end]
       @editSession.buffer.insert([row, 0], @editSession.getTabText()) unless @editSession.buffer.lineLengthForRow(row) == 0

+  # Public: ?
   setIndentationForLine: (line, indentLevel) ->
     desiredIndentLevel = Math.max(0, indentLevel)
     desiredIndentString = @editSession.buildIndentString(desiredIndentLevel)
     line.replace(/^[\t ]*/, desiredIndentString)

-  # Performs a backspace, removing the character found behind the selection.
+  # Public: Removes the first character before the selection if the selection
+  # is empty otherwise it deletes the selection.
   backspace: ->
     @selectLeft() if @isEmpty() and not @editSession.isFoldedAtScreenRow(@cursor.getScreenRow())
     @deleteSelectedText()

-  # Performs a backspace to the beginning of the current word, removing characters found there.
+  # Public: Removes from the start of the selection to the beginning of the
+  # current word if the selection is empty otherwise it deletes the selection.
   backspaceToBeginningOfWord: ->
     @selectToBeginningOfWord() if @isEmpty()
     @deleteSelectedText()

-  # Performs a backspace to the beginning of the current line, removing characters found there.
+  # Public: Removes from the beginning of the line which the selection begins on
+  # all the way through to the end of the selection.
   backspaceToBeginningOfLine: ->
     if @isEmpty() and @cursor.isAtBeginningOfLine()
       @selectLeft()
@@ -371,7 +406,8 @@
       @selectToBeginningOfLine()
     @deleteSelectedText()

-  # Performs a delete, removing the character found ahead of the cursor position.
+  # Public: Removes the selection or the next character after the start of the
+  # selection if the selection is empty.
   delete: ->
     if @isEmpty()
       if @cursor.isAtEndOfLine() and fold = @editSession.largestFoldStartingAtScreenRow(@cursor.getScreenRow() + 1)
@@ -380,12 +416,13 @@
         @selectRight()
     @deleteSelectedText()

-  # Performs a delete to the end of the current word, removing characters found there.
+  # Public: Removes the selection or all characters from the start of the
+  # selection to the end of the current word if nothing is selected.
   deleteToEndOfWord: ->
     @selectToEndOfWord() if @isEmpty()
     @deleteSelectedText()

-  # Deletes the selected text.
+  # Public: Removes only the selected text.
   deleteSelectedText: ->
     bufferRange = @getBufferRange()
     if bufferRange.isEmpty() and fold = @editSession.largestFoldContainingBufferRow(bufferRange.start.row)
@@ -393,7 +430,9 @@
     @editSession.buffer.delete(bufferRange) unless bufferRange.isEmpty()
     @cursor?.setBufferPosition(bufferRange.start)

-  # Deletes the line.
+  # Public: Removes the line at the beginning of the selection if the selection
+  # is empty unless the selection spans multiple lines in which case all lines
+  # are removed.
   deleteLine: ->
     if @isEmpty()
       start = @cursor.getScreenRow()
@@ -410,7 +449,7 @@
         end--
       @editSession.buffer.deleteRows(start, end)

-  # Joins the current line with the one below it.
+  # Public: Joins the current line with the one below it.
   #
   # If there selection spans more than one line, all the lines are joined together.
   joinLine: ->
@@ -438,6 +477,7 @@
       @setBufferRange(newSelectedRange)
       joinMarker.destroy()

+  # Public: Removes one level of indent from the currently selected rows.
   outdentSelectedRows: ->
     [start, end] = @getBufferRowRange()
     buffer = @editSession.buffer
@@ -446,33 +486,41 @@
       if matchLength = buffer.lineForRow(row).match(leadingTabRegex)?[0].length
         buffer.delete [[row, 0], [row, matchLength]]

+  # Public: Sets the indentation level of all selected rows to values suggested
+  # by the relevant grammars.
   autoIndentSelectedRows: ->
     [start, end] = @getBufferRowRange()
     @editSession.autoIndentBufferRows(start, end)

-  # Wraps the selected lines in comments.
-  #
-  # Returns an {Array} of the commented {Ranges}.
+  # Public: Wraps the selected lines in comments if they aren't currently part
+  # of a comment.
+  #
+  # Removes the comment if they are currently wrapped in a comment.
+  #
+  # Returns an Array of the commented {Range}s.
   toggleLineComments: ->
     @editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...)

-  # Performs a cut operation on the selection, until the end of the line.
-  #
-  # maintainPasteboard - A {Boolean} indicating TODO
+  # Public: Cuts the selection until the end of the line.
+  #
+  # * maintainPasteboard:
+  #   ?
   cutToEndOfLine: (maintainPasteboard) ->
     @selectToEndOfLine() if @isEmpty()
     @cut(maintainPasteboard)

-  # Performs a cut operation on the selection.
-  #
-  # maintainPasteboard - A {Boolean} indicating TODO
+  # Public: Copies the selection to the pasteboard and then deletes it.
+  #
+  # * maintainPasteboard:
+  #   ?
   cut: (maintainPasteboard=false) ->
     @copy(maintainPasteboard)
     @delete()

-  # Performs a copy operation on the selection.
-  #
-  # maintainPasteboard - A {Boolean} indicating TODO
+  # Public: Copies the current selection to the pasteboard.
+  #
+  # * maintainPasteboard:
+  #   ?
   copy: (maintainPasteboard=false) ->
     return if @isEmpty()
     text = @editSession.buffer.getTextInRange(@getBufferRange())
@@ -484,19 +532,20 @@

     pasteboard.write(text, metadata)

-  # Folds the selection.
+  # Public: Creates a fold containing the current selection.
   fold: ->
     range = @getBufferRange()
     @editSession.createFold(range.start.row, range.end.row)
     @cursor.setBufferPosition([range.end.row + 1, 0])

+  # Public: ?
   modifySelection: (fn) ->
     @retainSelection = true
     @plantTail()
     fn()
     @retainSelection = false

-  # Sets the marker's tail to the same position as the marker's head.
+  # Private: Sets the marker's tail to the same position as the marker's head.
   #
   # This only works if there isn't already a tail position.
   #
@@ -504,26 +553,31 @@
   plantTail: ->
     @marker.plantTail()

-  # Identifies if a selection intersects with a given buffer range.
-  #
-  # bufferRange - A {Range} to check against
-  #
-  # Returns a {Boolean}.
+  # Public: Identifies if a selection intersects with a given buffer range.
+  #
+  # * bufferRange:
+  #   A {Range} to check against
+  #
+  # Returns a Boolean.
   intersectsBufferRange: (bufferRange) ->
     @getBufferRange().intersectsWith(bufferRange)

-  # Identifies if a selection intersects with another selection.
-  #
-  # otherSelection - A `Selection` to check against
-  #
-  # Returns a {Boolean}.
+  # Public: Identifies if a selection intersects with another selection.
+  #
+  # * otherSelection:
+  #   A {Selection} to check against
+  #
+  # Returns a Boolean.
   intersectsWith: (otherSelection) ->
     @getBufferRange().intersectsWith(otherSelection.getBufferRange())

-  # Merges two selections together.
-  #
-  # otherSelection - A `Selection` to merge with
-  # options - A hash of options matching those found in {.setBufferRange}
+  # Public: Combines the given selection into this selection and then destroys
+  # the given selection.
+  #
+  # * otherSelection:
+  #   A {Selection} to merge with
+  # * options
+  #    + A hash of options matching those found in {.setBufferRange}
   merge: (otherSelection, options) ->
     myGoalBufferRange = @getGoalBufferRange()
     otherGoalBufferRange = otherSelection.getGoalBufferRange()
@@ -534,17 +588,19 @@
     @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)
     otherSelection.destroy()

+  # Public: ?
   compare: (other) ->
     @getBufferRange().compare(other.getBufferRange())

+  # Public: Returns true if it was locally created.
   isLocal: ->
     @marker.isLocal()

+  # Public: Returns true if it was created remotely.
   isRemote: ->
     @marker.isRemote()

-  ### Internal ###
-
+  # Private:
   screenRangeChanged: ->
     screenRange = @getScreenRange()
     @trigger 'screen-range-changed', screenRange


Properly pass url when paginating comments

--- app/js/views/comments.coffee
+++ app/js/views/comments.coffee
@@ -8,7 +8,7 @@

   # Fetch comments for the given url without reseting existing comments.
   fetch: (url = @collection.url) ->
-    @collection.fetch(reset: false, remove: false).done(@scroll).done(@paginate)
+    @collection.fetch(url: url, reset: false, remove: false).done(@scroll).done(@paginate)

   # Ajax callback to fetch next page of comments if the link exists.
   paginate: (data, options, xhr) =>


Update pt-BR.coffee (#6188)

Update pt-BR.coffee (#6188)

--- app/locale/pt-BR.coco.coffee
+++ app/locale/pt-BR.coco.coffee
@@ -34,7 +34,7 @@
     classroom_in_box_blurb3: "Inspirar todos os líderes da escola para criar um programa de ciência da computação de classe mundial."
 #    classroom_in_box_blurb4: ""
 #    covid19_banner_blurb: "We are offering free access to our full curriculum to educators and schools affected by school closures."
-#    click_here: "Click here"
+#   click_here: "Clique aqui"
     creativity_rigor_title: "Onde a criatividade encontra o rigor"
     creativity_rigor_subtitle1: "Torne a codificação divertida e ensine habilidades do mundo real"
     creativity_rigor_blurb1: "Os alunos digitam Python e JavaScript reais enquanto jogam jogos que incentivam a tentativa e erro, o pensamento crítico e a criatividade. Os alunos então aplicam as habilidades de codificação que aprenderam ao desenvolver seus próprios jogos e sites em cursos baseados em projetos."


Run copy-i18n-tags (#6500)

Run copy-i18n-tags (#6500)

Co-authored-by: CoCo CI <5df1af3d7600b97ee69552caffeccf9dfd29ab8f@codecombat.com>


Adding hypothesis counter changers. refs #8

Implementing dec/inc counters for hypothesis creation and deletion. For hypothesis status updates there is a custom method that would atomically change the status and change counters as needed, but this one was not tested yet.

--- app/models/hypothesis.coffee
+++ app/models/hypothesis.coffee
@@ -10,16 +10,46 @@
   criterion_total:   DS.attr 'number', defaultValue: 0
   criterion_valid:   DS.attr 'number', defaultValue: 0
   criterion_invalid: DS.attr 'number', defaultValue: 0
-  ## -1 = invalid; 0 = unfinished validation; 1 = valid!
   status:            DS.attr 'number', defaultValue: 0
   text:              DS.attr 'string'

-  defaultText: Ember.computed 'customer', 'problem', 'solution', 'risk', ->
-    text = @get('customer').title + 'pode' + @get('problem').title
-    if (solution = @get('solution'))
-      text += '. Resolve com' + solution.title
-    if (risk = @get('risk'))
-      text += (if solution then ', mas pode ' else 'E talvez possa ') + risk.title
-    return text
+  STATUS:
+    invalid: -1
+    pending: 0
+    valid: 1
+
+  getStatusStr: (num)->
+    _.findKey @STATUS, (v)-> v == status
+
+  #TODO: not yet used, so not tested
+  setStatus: (status)->
+    if status != @STATUS['valid'] && status != @STATUS['invalid'] && status != @STATUS['pending']
+      throw Error('Status field must be one of Hypothesis.STATUS values')
+
+    if @get('status') != status
+      old_status = @get 'status'
+      @set 'status', status
+      @save()
+      new_status_str = @getStatusStr status
+      old_status_str = @getStatusStr old_status
+      @get('board').then (board)->
+        board.incrementProperty "hyp_#{new_status_str}" if new_status_str != 'pending'
+        board.decrementProperty "hyp_#{old_status_str}" if old_status_str != 'pending'
+        board.save()
+
+  didCreate: ->
+    @get('board').then (board)->
+      board.incrementProperty 'hyp_total'
+      board.save()
+
+  didDelete: ->
+    @get('board').then (board)=>
+      if @get('status') == @STATUS['valid']
+        board.decrementProperty 'hyp_valid'
+      else if @get('status') == @STATUS['invalid']
+        board.decrementProperty 'hyp_invalid'
+
+      board.decrementProperty 'hyp_total'
+      board.save()

 `export default Hypothesis`


add content to about page for hhmi

add content to about page for hhmi

--- app/pages/about/default.cjsx
+++ app/pages/about/default.cjsx
@@ -35,7 +35,7 @@

Gorongosa Lion Project

-
The trail camera initiative started as a study led by the Gorongosa Lion Project, a team of international and Mozambican conservationists whose goal is to understand the status and recovery of the lion population after the devastating years of war in Mozambique. We are conducting the first formal large carnivore research and conservation initiative in the park since the 1970s, and our first priority has been to identify and track each individual lion and map the prides and coalitions that they belong to. Our research also includes studying the structure and dynamics of these prides and coalitions, reproduction and mortality, diet, genetics, disease exposure, and human impacts (including illegal hunting and habitat encroachment). By closely studying Gorongosa’s lion population, we can help forge effective conservation strategies for their long-term survival.

+
The trail camera initiative started as a study led by the Gorongosa Lion Project, a team of conservationists from Mozambique and around the world whose goal is to understand the status and recovery of the lion population after the devastating years of war in Mozambique. We are conducting the first formal large carnivore research and conservation initiative in the park since the 1970s, and our first priority is to identify and track each lion and map the prides and coalitions that they belong to. Our research also includes studying the structure and dynamics of these prides and coalitions, reproduction and mortality, diet, genetics, disease exposure, and human impacts (including illegal hunting and habitat encroachment). By closely studying Gorongosa’s lion population, we can help forge effective conservation strategies for their long-term survival.

One of our research tools is an array of over 50 trail cameras that capture photos of animals as they walk by. We use the data from these cameras to identify new lions, track their movement, and study the other species that interact with lions across the park. The cameras have also proved useful for identifying other animals in Gorongosa, including new elephants and species that have not been seen in Gorongosa in decades, such as hyenas and jackals.


Use switch when extracting options

Use switch when extracting options

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -179,13 +179,13 @@
   extractOptions: (args) ->
     options = {}
     for arg in args
-      type = typeof(arg)
-      if type is "function"
-        options.content = arg
-      else if type is "string" or type is "number"
-        options.text = arg.toString()
-      else
-        options.attributes = arg
+      switch typeof(arg)
+        when 'function'
+          options.content = arg
+        when 'string', 'number'
+          options.text = arg.toString()
+        else
+          options.attributes = arg
     options

 jQuery.fn.view = -> @data('view')


Log a message when the web api produces a 404 response.


socialcontroller: fix duplicate post bug when a new private channel is initialized

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -119,6 +119,8 @@
     new MessageEventManager {}, m

     KD.singletons.socialapi.cacheItem m
+
+    addToScreenMap {channelId: m.initialChannelId, body: m.body}

     return m


Home: registration form validation tooltips made optionally sticky

--- client/Home/registerform.coffee
+++ client/Home/registerform.coffee
@@ -3,6 +3,9 @@
   constructor: ->

     super
+
+    @email.setOption 'stickyTooltip', yes
+    @username.setOption 'stickyTooltip', yes

     @email.input.on    'focus', @bound 'handleFocus'
     @username.input.on 'focus', @bound 'handleFocus'


Minor tweak to sub

Minor tweak to sub

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -121,11 +121,11 @@
         $sprite = $pokemon.find('.sprite')
         $sprite.removeClass('fade')
         hideSub = ->
-          $substitute = $pokemon.find('.substitute')
+          $substitute = $pokemon.find('.substitute').first()
           if $substitute.length == 0
             setTimeout(hideSub, 20)
             return
-          move($substitute).set('opacity', 0).y(100)
+          move($substitute).set('opacity', 0).y(300)
             .then(-> $substitute.remove()).end()
         hideSub()


Collaboration: Listen once collaboration initializer events.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -701,13 +701,13 @@

     rtm.auth()

-    rtm.on 'ClientAuthenticated', =>
+    rtm.once 'ClientAuthenticated', =>
       hostName = if @amIHost then KD.nick() else @collaborationHost
       title    = "#{hostName}.#{machine.slug}.#{workspaceData.slug}"

       rtm.fetchFileByTitle title

-      rtm.on 'FileQueryFinished', (file) =>
+      rtm.once 'FileQueryFinished', (file) =>
         {result} = file
         return if result.selfLink.indexOf(title) is -1

@@ -724,7 +724,7 @@

     @rtm.getFile fileId

-    @rtm.on 'FileLoaded', (doc) =>
+    @rtm.once 'FileLoaded', (doc) =>
       @realTimeDoc = doc
       nickname     = KD.nick()


Client: Adjust hazard positioning a bit.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -358,10 +358,10 @@
         $div = $("<div/>").addClass("field-#{player} team-stealth-rock")
         if isFront
           [ oldX, oldY ] = [ "20%", "80%" ]
-          [ newX, newY ] = [ "61%", "33%" ]
+          [ newX, newY ] = [ "67%", "35%" ]
         else
           [ oldX, oldY ] = [ "80%", "20%" ]
-          [ newX, newY ] = [ "37%", "86%" ]
+          [ newX, newY ] = [ "34%", "81%" ]
         if @skip?
           $div.css(top: newY, left: newX, opacity: .5)
           $battlePane.prepend($div)
@@ -382,8 +382,8 @@
             when 0 then "41%"
             when 1 then "40%"
           newX = switch previousLayers
-            when 0 then "85%"
-            when 1 then "82%"
+            when 0 then "77%"
+            when 1 then "73%"
         else
           oldY = "20%"
           oldX = "80%"
@@ -391,8 +391,8 @@
             when 0 then "87%"
             when 1 then "86%"
           newX = switch previousLayers
-            when 0 then "15%"
-            when 1 then "18%"
+            when 0 then "23%"
+            when 1 then "27%"
         if @skip?
           $div.css(top: newY, left: newX, opacity: .5)
           $battlePane.prepend($div)


semi-faceted search form

semi-faceted search form

--- app/assets/javascripts/droom/widgets.js.coffee
+++ app/assets/javascripts/droom/widgets.js.coffee
@@ -415,13 +415,22 @@
   #
   $.fn.suggestion_form = (options) ->
     @each ->
-      new CaptiveForm @,
+      new CaptiveForm @,
         fast: true
         auto: false
         into: "#suggestion_box"
         history: false
     @
-
+
+  $.fn.faceting_search = (options) ->
+    defaults =
+      fast: ".facet"
+      history: true
+      threshold: 4
+    @each ->
+      new CaptiveForm @, _.extend(defaults, options)
+    @
+

   class CaptiveForm
     @default_options: {
@@ -449,10 +458,10 @@
         on_cancel: @cancel
         on_success: @capture
       @submit_soon = _.debounce(@submit, 500)
-      @bindInputs() if @_options.fast
+      @bindInputs(@_options.fast) if @_options.fast
       @_form.bind 'refresh', @changed
       if @_options.auto
-        @submit()
+        @submit()
       else
         @bindLinks()
       if @_historical
@@ -463,15 +472,17 @@
       @_container.find('a.cancel').click(@revert)
       @_container.find('.pagination a').click @page

-    bindInputs: () =>
-      @_form.find('input[type="search"]').bind 'keyup', @changed
-      @_form.find('input[type="search"]').bind 'change', @changed
-      # @_form.find('input[type="search"]').bind 'click', @changed  # for the clear-box control in webkit search fields
-      @_form.find('input[type="text"]').bind 'keyup', @keyed
-      @_form.find('input[type="text"]').bind 'change', @changed
-      @_form.find('select').bind 'change', @changed
-      @_form.find('input[type="radio"]').bind 'click', @clicked
-      @_form.find('input[type="checkbox"]').bind 'click', @clicked
+    bindInputs: (selector) =>
+      if typeof selector is "string"
+        @_form.find(selector).bind 'change', @changed
+      else
+        @_form.find('input[type="search"]').bind 'keyup', @changed
+        @_form.find('input[type="search"]').bind 'change', @changed
+        @_form.find('input[type="text"]').bind 'keyup', @keyed
+        @_form.find('input[type="text"]').bind 'change', @changed
+        @_form.find('select').bind 'change', @changed
+        @_form.find('input[type="radio"]').bind 'click', @clicked
+        @_form.find('input[type="checkbox"]').bind 'click', @clicked

     keyed: (e) =>
       k = e.which


Correctly shows label errors in dropdown Fixes #18344

--- app/assets/javascripts/labels_select.js.coffee
+++ app/assets/javascripts/labels_select.js.coffee
@@ -95,8 +95,11 @@
             $newLabelCreateButton.enable()

             if label.message?
+              errors = _.map label.message, (value, key) ->
+                "#{key} #{value[0]}"
+
               $newLabelError
-                .text label.message
+                .html errors.join("<br/>")
                 .show()
             else
               $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'


Refactor

Refactor

--- src/win32/api.coffee
+++ src/win32/api.coffee
@@ -11,6 +11,7 @@
   @DLL = ffi.Library 'advapi32.dll', {
     RegOpenKeyExW: [@TYPES.LONG, [@TYPES.ULONG, @TYPES.LPCWSTR, @TYPES.DWORD,
       @TYPES.REGSAM, @TYPES.PHKEY]]
+    RegCloseKey: [@TYPES.LONG, [@TYPES.HKEY]]
     RegQueryInfoKeyW: [@TYPES.LONG, [@TYPES.HKEY, @TYPES.LPWSTR,
       @TYPES.PDWORD, @TYPES.PDWORD, @TYPES.PDWORD, @TYPES.PDWORD,
       @TYPES.PDWORD, @TYPES.PDWORD, @TYPES.PDWORD, @TYPES.PDWORD,
@@ -19,7 +20,6 @@
     #   @PDWORD, @PFILETIME]]
     # RegEnumValueW: [@LONG, [@HKEY, @DWORD, @LPWSTR, @PDWORD, @PDWORD, @PDWORD,
     #   @LPBYTE, @PDWORD]]
-    RegCloseKey: [@TYPES.LONG, [@TYPES.HKEY]]
   }

   # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724897(v=vs.85).aspx


Cleaned up javascript

Cleaned up javascript

--- app/assets/javascripts/worker_status.js.coffee
+++ app/assets/javascripts/worker_status.js.coffee
@@ -4,25 +4,23 @@
     $.ajax("/queue_status", $.extend({}, cache: false))
       .done( (data) ->
         if data == 'idle' && false
+          text = "Workers Idle"
           color = "#AAFFAA"
-          text = "Workers Idle"
           text_color = "#777777"
           shadow = "0 1px 0 #FFFFFF"
         else if data == 'working'
+          text = "Workers Busy"
           color = "#FFFFAA"
-          text = "Workers Busy"
           text_color = "#777777"
           shadow = "0 1px 0 #FFFFFF"
         else if data == 'heavy' || true
+          text = "Workers Swamped"
           color = "#FFAAAA"
-          text = "Workers Swamped"
           text_color = "#666666"
           shadow = "0 1px 0 #AAAAAA"

         $('.worker-status').text(text)
-        $('.worker-status').css("background", color)
-        $('.worker-status').css("color", text_color)
-        $('.worker-status').css("text-shadow", shadow)
+        $('.worker-status').css({background: color, color: text_color, textShadow: shadow})
       );

   setInterval(checkWorkerStatus , 5 * 1000)


Remove lint warning

Remove lint warning

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -823,7 +823,7 @@
     task = Task.once require.resolve('./scan-handler'), atom.project.getPaths()[0], regex.source, searchOptions, ->
       deferred.resolve()

-    task.on 'scan:result-found', (result) =>
+    task.on 'scan:result-found', (result) ->
       iterator(result) unless atom.project.isPathModified(result.filePath)

     task.on 'scan:file-error', (error) ->


refactored coffeescript

refactored coffeescript

--- drm-popovers.coffee
+++ drm-popovers.coffee
@@ -1,44 +1,38 @@
 ###############################################################################
 # Displays popovers on hover
 ###############################################################################
+"use strict"

 ( ($) ->
+    class window.DrmPopover
+        constructor: (@holder = $('div.popover-holder')) ->
+            buttons = @holder.find 'button'
+            popovers = @holder.find 'div.drm-popover'

-	drmPopover = {
-		holders: $ '.popover-holder'
+            buttons.on 'click', @togglePopover
+            $('html').click -> popovers.hide()

-		init: ->
-			buttons = @.holders.find 'button'
-			popovers = @.holders.find '.drm-popover'
+        togglePopover: (e) ->
+            popoverId = $(@).data 'popover'
+            popover = $("div##{popoverId}").fadeToggle()
+
+            e.stopPropagation()

-			buttons.on 'click', @.togglePopover
+            checkPosition = ->
+                that = $ @
+                positionLeft = that.position().left
+                offsetLeft = that.offset().left
+                positionTop = that.position().top
+                offsetTop = that.offset().top
+                popoverHeight = that.height()

-			$('html').click ->
-				popovers.hide()
+                if offsetLeft < 0
+                    that.css('left': (Math.abs(offsetLeft) + 10) + positionLeft)
+                else if offsetTop < 0
+                    that.css('bottom': (Math.abs(positionTop) - popoverHeight) - Math.abs(offsetTop))

-		togglePopover: (e) ->
-			popoverId = $(@).data 'popover'
-			popover = $("##{popoverId}")
+            checkPosition.call popover

-			popover.fadeToggle()
-			e.stopPropagation()
-
-			drmPopover.checkPosition.call popover
-
-		checkPosition: ->
-			that = $ @
-			positionLeft = that.position().left
-			offsetLeft = that.offset().left
-			positionTop = that.position().top
-			offsetTop = that.offset().top
-			popoverHeight = that.height()
-
-			if offsetLeft < 0
-				that.css('left': (Math.abs(offsetLeft) + 10) + positionLeft)
-			else if offsetTop < 0
-				that.css('bottom': (Math.abs(positionTop) - popoverHeight) - Math.abs(offsetTop))
-	}
-
-	drmPopover.init()
+    new DrmPopover()

 ) jQuery


tests: saved another 5 secs per test by not waiting for IDE

since it's not necessary after state machine refactor

--- client/test/lib/helpers/helpers.coffee
+++ client/test/lib/helpers/helpers.coffee
@@ -221,7 +221,8 @@
   doPostActivity: (browser, post, shouldAssert = yes, hasEmbeddable = no) ->

     browser
-      .pause                    5000 # wait for IDE open
+      # not necessary anymore
+      # .pause                    5000 # wait for IDE open
       .click                    '[testpath="public-feed-link/Activity/Topic/public"]'
       .waitForElementVisible    '[testpath=ActivityInputView]', 10000
       .click                    '[testpath="ActivityTabHandle-/Activity/Public/Recent"] a'


Fix saving with null plan and customer

Fix saving with null plan and customer

--- coffee/subscriptions.controller.coffee
+++ coffee/subscriptions.controller.coffee
@@ -198,12 +198,11 @@
         @analytics.ecAddToCart(planId, name, amount)
         @analytics.ecConfirmChange(planId, name, amount)

-        if @.myPlan && @.myPlan.customer_id?
+        if @.myPlan?.current_plan && @.myPlan.customer_id?
             plan = {
                 'plan_id': planId,
                 'quantity': (@.perSeatPlan.members.length || 1)
             }
-
             @._onSuccessBuyPlan(plan, amount, currency)
         else
             @paymentsService.start({
@@ -223,7 +222,7 @@
         @lightboxService.closeAll()
         @tgLoader.start()

-        if !@.myPlan || (!@.myPlan.current_plan.amount_month && !@.myPlan.current_plan.amount_year)
+        if !@.myPlan || (!@.myPlan.current_plan?.amount_month && !@.myPlan.current_plan?.amount_year)
             google_conversion_id = @config.get("google_adwords_conversion_id")
             google_conversion_label = @config.get("google_adwords_conversion_label")


added getCurrentState() / setSlideState()

--- lib/app.coffee
+++ lib/app.coffee
@@ -6,6 +6,9 @@
 utils = require "./utils"

 CURRENT_LOCAL_SLIDE_STATE = null
+setSlideState = (state) ->
+  CURRENT_LOCAL_SLIDE_STATE = state
+exports.getCurrentState = () -> CURRENT_LOCAL_SLIDE_STATE

 ################################################################################

@@ -32,7 +35,7 @@
     ev = stateChange.event
     ev.isRemote = true
     remoteIdx = _.pick(stateChange.prevState.slide, ['h', 'v'])
-    localSlideIdx = _.pick(CURRENT_LOCAL_SLIDE_STATE.slide, ['h', 'v'])
+    localSlideIdx = _.pick(getCurrentState().slide, ['h', 'v'])
     if remoteIdx.toString() != localSlideIdx.toString()
       # out of sync with remote, msgs must have been dropped.
       # sync first before replaying event.
@@ -55,7 +58,7 @@
     streams.localSlideStateChangeStream.onNext(
       new core.StateChange(
         event: ev, prevState: prevState, newState: newState))
-    CURRENT_LOCAL_SLIDE_STATE = newState
+    setSlideState(newState)
     newState
   catch err
     net.log(err) #TODO: replace this with an error record
@@ -70,7 +73,7 @@
   window.mainSlideDeck = deck = revealjsDomToSlideDeck()
   initSlide = deck.get(Reveal.getIndices())
   initState = new core.PresentationState(deck, initSlide)
-  CURRENT_LOCAL_SLIDE_STATE = initState
+  setSlideState(initState)
   initState

 initEventstreamSubscriptions = ->