test(mapping): fix broken mapping tests

test(mapping): fix broken mapping tests

--- src/spec/mapping.spec.coffee
+++ src/spec/mapping.spec.coffee
@@ -57,137 +57,166 @@
       expect(_.size @map.errors).toBe 1
       expect(@map.errors[0]).toBe "[row 7:slug] Can't generate valid slug out of '1'!"

-  # describe '#mapLocalizedAttrib', ->
-  #   it 'should create mapping for language attributes', ->
-  #     csv =
-  #       """
-  #       foo,name.de,bar,name.it
-  #       x,Hallo,y,ciao
-  #       """
-  #     @validator.parse csv, (content) =>
-  #       values = @map.mapLocalizedAttrib content[0], CONS.HEADER_NAME, @validator.header.toLanguageIndex()
-  #       expect(_.size values).toBe 2
-  #       expect(values['de']).toBe 'Hallo'
-  #       expect(values['it']).toBe 'ciao'
-  #
-  #   it 'should fallback to non localized column', ->
-  #     csv =
-  #       """
-  #       foo,a1,bar
-  #       x,hi,y
-  #       aaa,,bbb
-  #       """
-  #     @validator.parse csv, (content, count) =>
-  #       @validator.header.toIndex()
-  #       values = @map.mapLocalizedAttrib(content[0], 'a1', {})
-  #       expect(_.size values).toBe 1
-  #       expect(values['en']).toBe 'hi'
-  #
-  #       values = @map.mapLocalizedAttrib(content[1], 'a1', {})
-  #       expect(values).toBeUndefined()
-  #
-  #   it 'should return undefined if header can not be found', ->
-  #     csv =
-  #       """
-  #       foo,a1,bar
-  #       x,hi,y
-  #       """
-  #     @validator.parse csv, (content, count) =>
-  #       @validator.header.toIndex()
-  #       values = @map.mapLocalizedAttrib(content[0], 'a2', {})
-  #       expect(values).toBeUndefined()
-  #
-  # describe '#mapBaseProduct', ->
-  #   it 'should map base product', ->
-  #     csv =
-  #       """
-  #       productType,id,name,variantId
-  #       foo,xyz,myProduct,1
-  #       """
-  #     pt =
-  #       id: '123'
-  #     @validator.parse csv, (content, count) =>
-  #       @validator.validateOffline content
-  #       product = @map.mapBaseProduct @validator.rawProducts[0].master, pt
-  #
-  #       expectedProduct =
-  #         id: 'xyz'
-  #         productType:
-  #           typeId: 'product-type'
-  #           id: '123'
-  #         name:
-  #           en: 'myProduct'
-  #         slug:
-  #           en: 'myproduct'
-  #         masterVariant: {}
-  #         variants: []
-  #         categories: []
-  #
-  #       expect(product).toEqual expectedProduct
-  #
-  #   it 'should map base product with categories', ->
-  #     csv =
-  #       """
-  #       productType,id,name,variantId,categories.externalId
-  #       foo,xyz,myProduct,1,ext-123
-  #       """
-  #     pt =
-  #       id: '123'
-  #     cts = [
-  #       id: '234'
-  #       externalId: 'ext-123'
-  #     ]
-  #
-  #     @validator.parse csv, (content, count) =>
-  #       @categories = new Categories
-  #       @categories.buildMaps cts
-  #       @validator.validateOffline content
-  #       product = @map.mapBaseProduct @validator.rawProducts[0].master, pt
-  #
-  #       expectedProduct =
-  #         id: 'xyz'
-  #         productType:
-  #           typeId: 'product-type'
-  #           id: '123'
-  #         name:
-  #           en: 'myProduct'
-  #         slug:
-  #           en: 'myproduct'
-  #         masterVariant: {}
-  #         variants: []
-  #         categories: [
-  #           typeId: 'category'
-  #           id: '234'
-  #         ]
-  #
-  #       expect(product).toEqual expectedProduct
-  #   it 'should map search keywords', ->
-  #     csv =
-  #     """
-  #       productType,variantId,id,searchKeywords.en,searchKeywords.fr-FR
-  #       product-type,1,xyz,some;new;search;keywords,bruxelle;liege;brugge,
-  #       """
-  #     pt =
-  #       id: '123'
-  #     @validator.parse csv, (content, count) =>
-  #       @validator.validateOffline content
-  #       product = @map.mapBaseProduct @validator.rawProducts[0].master, pt
-  #
-  #       expectedProduct =
-  #         id: 'xyz'
-  #         productType:
-  #           typeId: 'product-type'
-  #           id: '123'
-  #         name:
-  #           en: 'myProduct'
-  #         slug:
-  #           en: 'myproduct'
-  #         masterVariant: {}
-  #         variants: []
-  #         categories: []
-  #         searchKeywords: {"en":[{"text":"some"},{"text":"new"},{"text":"search"},{"text":"keywords"}],"fr-FR":[{"text":"bruxelle"},{"text":"liege"},{"text":"brugge"}]}
-  #
-  #       expect(product).toEqual expectedProduct
+  describe '#mapLocalizedAttrib', ->
+    it 'should create mapping for language attributes', (done) ->
+      csv =
+        """
+        foo,name.de,bar,name.it
+        x,Hallo,y,ciao
+        """
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        values = @map.mapLocalizedAttrib parsed.data[0], CONS.HEADER_NAME, @validator.header.toLanguageIndex()
+        expect(_.size values).toBe 2
+        expect(values['de']).toBe 'Hallo'
+        expect(values['it']).toBe 'ciao'
+        done()
+      .catch done
+
+    it 'should fallback to non localized column', (done) ->
+      csv =
+        """
+        foo,a1,bar
+        x,hi,y
+        aaa,,bbb
+        """
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        @validator.header.toIndex()
+        values = @map.mapLocalizedAttrib(parsed.data[0], 'a1', {})
+        expect(_.size values).toBe 1
+        expect(values['en']).toBe 'hi'
+
+        values = @map.mapLocalizedAttrib(parsed.data[1], 'a1', {})
+        expect(values).toBeUndefined()
+        done()
+      .catch done
+
+    it 'should return undefined if header can not be found', (done) ->
+      csv =
+        """
+        foo,a1,bar
+        x,hi,y
+        """
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        @validator.header.toIndex()
+        values = @map.mapLocalizedAttrib(parsed.data[0], 'a2', {})
+        expect(values).toBeUndefined()
+        done()
+      .catch done
+
+  describe '#mapBaseProduct', ->
+    it 'should map base product', (done) ->
+      csv =
+        """
+        productType,id,name,variantId
+        foo,xyz,myProduct,1
+        """
+      pt =
+        id: '123'
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        @validator.validateOffline parsed.data
+        product = @map.mapBaseProduct @validator.rawProducts[0].master, pt
+
+        expectedProduct =
+          id: 'xyz'
+          productType:
+            typeId: 'product-type'
+            id: '123'
+          name:
+            en: 'myProduct'
+          slug:
+            en: 'myproduct'
+          masterVariant: {}
+          variants: []
+          categories: []
+
+        expect(product).toEqual expectedProduct
+        done()
+      .catch done
+
+    it 'should map base product with categories', (done) ->
+      csv =
+        """
+        productType,id,name,variantId,categories
+        foo,xyz,myProduct,1,ext-123
+        """
+      pt =
+        id: '123'
+      cts = [
+        id: '234'
+        name:
+          en: 'mockName'
+        slug:
+          en: 'mockSlug'
+        externalId: 'ext-123'
+      ]
+
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        @categories = new Categories
+        @categories.buildMaps cts
+        @map.categories = @categories
+        @validator.validateOffline parsed.data
+        product = @map.mapBaseProduct @validator.rawProducts[0].master, pt
+
+        expectedProduct =
+          id: 'xyz'
+          productType:
+            typeId: 'product-type'
+            id: '123'
+          name:
+            en: 'myProduct'
+          slug:
+            en: 'myproduct'
+          masterVariant: {}
+          variants: []
+          categories: [
+            typeId: 'category'
+            id: '234'
+          ]
+
+        expect(product).toEqual expectedProduct
+        done()
+      .catch done
+    it 'should map search keywords', (done) ->
+      csv =
+      """
+        productType,variantId,id,name.en,slug.en,searchKeywords.en,searchKeywords.fr-FR
+        product-type,1,xyz,myProduct,myproduct,some;new;search;keywords,bruxelle;liege;brugge,
+        """
+      pt =
+        id: '123'
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        @validator.validateOffline parsed.data
+        product = @map.mapBaseProduct @validator.rawProducts[0].master, pt
+
+        expectedProduct =
+          id: 'xyz'
+          productType:
+            typeId: 'product-type'
+            id: '123'
+          name:
+            en: 'myProduct'
+          slug:
+            en: 'myproduct'
+          masterVariant: {}
+          variants: []
+          categories: []
+          searchKeywords: {"en":[{"text":"some"},{"text":"new"},{"text":"search"},{"text":"keywords"}],"fr-FR":[{"text":"bruxelle"},{"text":"liege"},{"text":"brugge"}]}
+
+        expect(product).toEqual expectedProduct
+        done()
+      .catch done

   describe '#mapVariant', ->
     it 'should give feedback on bad variant id', ->
@@ -554,41 +583,45 @@
           referenceTypeId: 'product'
       expect(@map.mapReference('123-456', attribute)).toEqual { id: '123-456', typeId: 'product' }

-  # describe '#mapProduct', ->
-  #   it 'should map a product', ->
-  #     productType =
-  #       id: 'myType'
-  #       attributes: []
-  #     csv =
-  #       """
-  #       productType,name,variantId,sku
-  #       foo,myProduct,1,x
-  #       ,,2,y
-  #       ,,3,z
-  #       """
-  #     @validator.parse csv, (content, count) =>
-  #       @validator.validateOffline content
-  #       data = @map.mapProduct @validator.rawProducts[0], productType
-  #
-  #       expectedProduct =
-  #         productType:
-  #           typeId: 'product-type'
-  #           id: 'myType'
-  #         name:
-  #           en: 'myProduct'
-  #         slug:
-  #           en: 'myproduct'
-  #         categories: []
-  #         masterVariant: {
-  #           id: 1
-  #           sku: 'x'
-  #           prices: []
-  #           attributes: []
-  #           images: []
-  #         }
-  #         variants: [
-  #           { id: 2, sku: 'y', prices: [], attributes: [], images: [] }
-  #           { id: 3, sku: 'z', prices: [], attributes: [], images: [] }
-  #         ]
-  #
-  #       expect(data.product).toEqual expectedProduct
+  describe '#mapProduct', ->
+    it 'should map a product', (done) ->
+      productType =
+        id: 'myType'
+        attributes: []
+      csv =
+        """
+        productType,name,variantId,sku
+        foo,myProduct,1,x
+        ,,2,y
+        ,,3,z
+        """
+      @validator.parse csv
+      .then (parsed) =>
+        @map.header = parsed.header
+        @validator.validateOffline parsed.data
+        data = @map.mapProduct @validator.rawProducts[0], productType
+
+        expectedProduct =
+          productType:
+            typeId: 'product-type'
+            id: 'myType'
+          name:
+            en: 'myProduct'
+          slug:
+            en: 'myproduct'
+          categories: []
+          masterVariant: {
+            id: 1
+            sku: 'x'
+            prices: []
+            attributes: []
+            images: []
+          }
+          variants: [
+            { id: 2, sku: 'y', prices: [], attributes: [], images: [] }
+            { id: 3, sku: 'z', prices: [], attributes: [], images: [] }
+          ]
+
+        expect(data.product).toEqual expectedProduct
+        done()
+      .catch done


Projects are now opened using atom

Projects are now opened using atom

--- lib/project-manager.coffee
+++ lib/project-manager.coffee
@@ -1,6 +1,5 @@
 ProjectManagerView = require './project-manager-view'
 ProjectManagerAddView = require './project-manager-add-view'
-{exec} = require 'child_process'

 module.exports =
   projectManagerView: null
@@ -29,5 +28,6 @@
     atom.config.get("project-manager")

   openProject: (title) ->
-    path = @getProjects()[title]
-    open = exec "open -a Atom.app #{path}" if path?+    paths = @getProjects()[title].split(',')
+    atom.open options =
+      pathsToOpen: paths if paths?


cleanup further

cleanup further

--- lib/provider/search.coffee
+++ lib/provider/search.coffee
@@ -73,26 +73,26 @@
       options: options
     )

+getProjectDirectoryForFilePath = (filePath) ->
+  return null unless filePath?
+  for dir in atom.project.getDirectories() when dir.contains(filePath)
+    return dir
+  null
+
 module.exports =
 class Search extends SearchBase
   propertiesToRestoreOnReopen: ['projects']

   checkReady: ->
-    projects = null
     if @options.currentProject
-      filePath = @editor.getPath()
-      if filePath?
-        for dir in atom.project.getDirectories() when dir.contains(filePath)
-          projects = [dir.getPath()]
-          break
-
-      unless projects?
+      if dir = getProjectDirectoryForFilePath(@editor.getPath())
+        @projects = [dir.getPath()]
+      else
         message = "This file is not belonging to any project"
         atom.notifications.addInfo(message, dismissable: true)
         return Promise.resolve(false)

-    @projects ?= projects ? atom.project.getPaths()
-
+    @projects ?= atom.project.getPaths()
     super

   getArgs: ->


Add decorations for existing markers on create

Closes #14

--- lib/react-bookmarks.coffee
+++ lib/react-bookmarks.coffee
@@ -13,6 +13,8 @@
     @subscribeToCommand editorView, 'bookmarks:jump-to-previous-bookmark', @jumpToPreviousBookmark
     @subscribeToCommand editorView, 'bookmarks:clear-bookmarks', @clearBookmarks

+    @addDecorationsForBookmarks()
+
   toggleBookmark: =>
     cursors = @editor.getCursors()
     for cursor in cursors
@@ -23,6 +25,12 @@
         bookmark.destroy() for bookmark in bookmarks
       else
         @createBookmarkMarker(position.row)
+
+  addDecorationsForBookmarks: =>
+    for bookmark in @findBookmarkMarkers() when bookmark.isValid()
+      @editor.addDecorationForMarker(bookmark, {type: 'gutter', class: 'bookmarked'})
+
+    null

   clearBookmarks: =>
     bookmark.destroy() for bookmark in @findBookmarkMarkers()


change default route to dashboard

change default route to dashboard

--- src/router.coffee
+++ src/router.coffee
@@ -7,7 +7,7 @@

   router =
     <Routes location='history'>
-      <Redirect path='/' to='home' />
+      <Redirect path='/' to='dashboard' />
       <Route path='/' handler={App}>
         <Route path='dashboard' name='dashboard' handler={Dashboard} />
         <Route path='tasks' name='tasks' handler={Tasks} />


router: add fixme

router: add fixme

--- src/router.coffee
+++ src/router.coffee
@@ -129,6 +129,8 @@
             else
               $component?.state

+          # FIXME: This does not take into account components in other
+          # parts of the full tree (e.g. <head>).
           getState(@$lastRoot)?._unbind_subscriptions()
           @$lastRoot = $component
           getState(@$lastRoot)?._bind_subscriptions()


syntax inline

syntax inline

--- coffee/codemirror-syntax.coffee
+++ coffee/codemirror-syntax.coffee
@@ -1,523 +1,117 @@
 CodeMirror.defineMode "docscript", ((cmCfg, modeCfg) ->
-  getMode = (name) ->
-    if CodeMirror.findModeByName
-      found = CodeMirror.findModeByName(name)
-      name = found.mime or found.mimes[0]  if found
-    mode = CodeMirror.getMode(cmCfg, name)
-    (if mode.name is "null" then null else mode)
+  operator = "def strong"
+  operatorInline = "keyword"
+  console.log cmCfg
+  htmlMode = CodeMirror.getMode cmCfg, name: "xml", htmlMode: true

-  switchInline = (stream, state, f) ->
-    state.f = state.inline = f
-    f stream, state
-  switchBlock = (stream, state, f) ->
-    state.f = state.block = f
-    f stream, state
+  matchBlock = (stream, state) ->
+   stack = state.stack

-  # Blocks
-  blankLine = (state) ->
+   match = stream.match /^<<</
+   if match
+    stack.push indentation: stream.indentation(), type: 'html'
+    stream.skipToEnd()
+    state.htmlState = CodeMirror.startState htmlMode
+    return operator

-    # Reset linkTitle state
-    state.linkTitle = false
+   match = stream.match /^\+\+\+/
+   if match
+    stack.push indentation: stream.indentation(), type: 'special'
+    stream.skipToEnd()
+    return operator

-    # Reset EM state
-    state.em = false
+   match = stream.match /^>>>/
+   if match
+    stack.push indentation: stream.indentation(), type: 'sidenote'
+    stream.skipToEnd()
+    return operator

-    # Reset STRONG state
-    state.strong = false
+   match = stream.match /^```/
+   if match
+    stack.push indentation: stream.indentation(), type: 'code'
+    stream.skipToEnd()
+    return operator

-    # Reset strikethrough state
-    state.strikethrough = false
+   return null

-    # Reset state.quote
-    state.quote = 0
-    if not htmlFound and state.f is htmlBlock
-      state.f = inlineNormal
-      state.block = blockNormal
+  matchInline = (stream, state) ->
+   match = stream.match /^\*\*/
+   if match
+    state.bold = not state.bold
+    return operatorInline

-    # Reset state.trailingSpace
-    state.trailingSpace = 0
-    state.trailingSpaceNewLine = false
+   match = stream.match /^--/
+   if match
+    state.italics = not state.italics
+    return operatorInline

-    # Mark this line as blank
-    state.thisLineHasContent = false
-    null
+   match = stream.match /^__/
+   if match
+    state.subscript = not state.subscript
+    return operatorInline

-  blockNormal = (stream, state) ->
-    sol = stream.sol()
-    prevLineIsList = (state.list isnt false)
-    if state.list isnt false and state.indentationDiff >= 0 # Continued list
-      # Only adjust indentation if *not* a code block
-      state.indentation -= state.indentationDiff  if state.indentationDiff < 4
-      state.list = null
-    else if state.list isnt false and state.indentation > 0
-      state.list = null
-      state.listDepth = Math.floor(state.indentation / 4)
-    else if state.list isnt false # No longer a list
-      state.list = false
-      state.listDepth = 0
-    match = null
-    if state.indentationDiff >= 4
-      state.indentation -= 4
-      stream.skipToEnd()
-      return code
-    else if stream.eatSpace()
-      return null
-    else
-      match = stream.match(atxHeaderRE)
-      if match
-        state.header = (if match[0].length <= 6 then match[0].length else 6)
-        state.formatting = "header"  if modeCfg.highlightFormatting
-        state.f = state.inline
-        return getType(state)
-      else if state.prevLineHasContent and (match = stream.match(setextHeaderRE))
-        state.header = (if match[0].charAt(0) is "=" then 1 else 2)
-        state.formatting = "header"  if modeCfg.highlightFormatting
-        state.f = state.inline
-        return getType(state)
-      else if stream.eat(">")
-        state.indentation++
-        state.quote = (if sol then 1 else state.quote + 1)
-        state.formatting = "quote"  if modeCfg.highlightFormatting
-        stream.eatSpace()
-        return getType(state)
-      else if stream.peek() is "["
-        return switchInline(stream, state, footnoteLink)
-      else if stream.match(hrRE, true)
-        return hr
-      else if (not state.prevLineHasContent or prevLineIsList) and (stream.match(ulRE, false) or stream.match(olRE, false))
-        listType = null
-        if stream.match(ulRE, true)
-          listType = "ul"
-        else
-          stream.match olRE, true
-          listType = "ol"
-        state.indentation += 4
-        state.list = true
-        state.listDepth++
-        state.taskList = true  if modeCfg.taskLists and stream.match(taskListRE, false)
-        state.f = state.inline
-        state.formatting = [ "list", "list-" + listType ]  if modeCfg.highlightFormatting
-        return getType(state)
-      else if modeCfg.fencedCodeBlocks and stream.match(/^```[ \t]*([\w+#]*)/, true)
+   match = stream.match /^\^\^/
+   if match
+    state.superscript = not state.superscript
+    return operatorInline

-        # try switching mode
-        state.localMode = getMode(RegExp.$1)
-        state.localState = state.localMode.startState()  if state.localMode
-        state.f = state.block = local
-        state.formatting = "code-block"  if modeCfg.highlightFormatting
-        state.code = true
-        return getType(state)
-    switchInline stream, state, state.inline
-  htmlBlock = (stream, state) ->
-    style = htmlMode.token(stream, state.htmlState)
-    if (htmlFound and state.htmlState.tagStart is null and not state.htmlState.context) or (state.md_inside and stream.current().indexOf(">") > -1)
-      state.f = inlineNormal
-      state.block = blockNormal
-      state.htmlState = null
-    style
-  local = (stream, state) ->
-    if stream.sol() and stream.match("```", false)
-      state.localMode = state.localState = null
-      state.f = state.block = leavingLocal
-      null
-    else if state.localMode
-      state.localMode.token stream, state.localState
-    else
-      stream.skipToEnd()
-      code
-  leavingLocal = (stream, state) ->
-    stream.match "```"
-    state.block = blockNormal
-    state.f = inlineNormal
-    state.formatting = "code-block"  if modeCfg.highlightFormatting
-    state.code = true
-    returnType = getType(state)
-    state.code = false
-    returnType
+   match = stream.match /^``/
+   if match
+    state.code = not state.code
+    return operatorInline

-  # Inline
-  getType = (state) ->
-    styles = []
-    if state.formatting
-      styles.push formatting
-      state.formatting = [ state.formatting ]  if typeof state.formatting is "string"
-      i = 0
+   match = stream.match /^<</
+   if match
+    state.link = true
+    return operatorInline

-      while i < state.formatting.length
-        styles.push formatting + "-" + state.formatting[i]
-        styles.push formatting + "-" + state.formatting[i] + "-" + state.header  if state.formatting[i] is "header"
-
-        # Add `formatting-quote` and `formatting-quote-#` for blockquotes
-        # Add `error` instead if the maximum blockquote nesting depth is passed
-        if state.formatting[i] is "quote"
-          if not modeCfg.maxBlockquoteDepth or modeCfg.maxBlockquoteDepth >= state.quote
-            styles.push formatting + "-" + state.formatting[i] + "-" + state.quote
-          else
-            styles.push "error"
-        i++
-    if state.taskOpen
-      styles.push "meta"
-      return (if styles.length then styles.join(" ") else null)
-    if state.taskClosed
-      styles.push "property"
-      return (if styles.length then styles.join(" ") else null)
-    if state.linkHref
-      styles.push linkhref
-      return (if styles.length then styles.join(" ") else null)
-    styles.push strong  if state.strong
-    styles.push em  if state.em
-    styles.push strikethrough  if state.strikethrough
-    styles.push linktext  if state.linkText
-    styles.push code  if state.code
-    if state.header
-      styles.push header
-      styles.push header + "-" + state.header
-    if state.quote
-      styles.push quote
-
-      # Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
-      if not modeCfg.maxBlockquoteDepth or modeCfg.maxBlockquoteDepth >= state.quote
-        styles.push quote + "-" + state.quote
-      else
-        styles.push quote + "-" + modeCfg.maxBlockquoteDepth
-    if state.list isnt false
-      listMod = (state.listDepth - 1) % 3
-      unless listMod
-        styles.push list1
-      else if listMod is 1
-        styles.push list2
-      else
-        styles.push list3
-    if state.trailingSpaceNewLine
-      styles.push "trailing-space-new-line"
-    else styles.push "trailing-space-" + ((if state.trailingSpace % 2 then "a" else "b"))  if state.trailingSpace
-    (if styles.length then styles.join(" ") else null)
-  handleText = (stream, state) ->
-    return getType(state)  if stream.match(textRE, true)
-    `undefined`
-  inlineNormal = (stream, state) ->
-    t = undefined
-    type = undefined
-    style = state.text(stream, state)
-    return style  if typeof style isnt "undefined"
-    if state.list # List marker (*, +, -, 1., etc)
-      state.list = null
-      return getType(state)
-    if state.taskList
-      taskOpen = stream.match(taskListRE, true)[1] isnt "x"
-      if taskOpen
-        state.taskOpen = true
-      else
-        state.taskClosed = true
-      state.formatting = "task"  if modeCfg.highlightFormatting
-      state.taskList = false
-      return getType(state)
-    state.taskOpen = false
-    state.taskClosed = false
-    if state.header and stream.match(/^#+$/, true)
-      state.formatting = "header"  if modeCfg.highlightFormatting
-      return getType(state)
-
-    # Get sol() value now, before character is consumed
-    sol = stream.sol()
-    ch = stream.next()
-    if ch is "\\"
-      stream.next()
-      if modeCfg.highlightFormatting
-        type = getType(state)
-        return (if type then type + " formatting-escape" else "formatting-escape")
-
-    # Matches link titles present on next line
-    if state.linkTitle
-      state.linkTitle = false
-      matchCh = ch
-      matchCh = ")"  if ch is "("
-      matchCh = (matchCh + "").replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1")
-      regex = "^\\s*(?:[^" + matchCh + "\\\\]+|\\\\\\\\|\\\\.)" + matchCh
-      return linkhref  if stream.match(new RegExp(regex), true)
-
-    # If this block is changed, it may need to be updated in GFM mode
-    if ch is "`"
-      previousFormatting = state.formatting
-      state.formatting = "code"  if modeCfg.highlightFormatting
-      t = getType(state)
-      before = stream.pos
-      stream.eatWhile "`"
-      difference = 1 + stream.pos - before
-      unless state.code
-        codeDepth = difference
-        state.code = true
-        return getType(state)
-      else
-        if difference is codeDepth # Must be exact
-          state.code = false
-          return t
-        state.formatting = previousFormatting
-        return getType(state)
-    else return getType(state)  if state.code
-    if ch is "!" and stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)
-      stream.match /\[[^\]]*\]/
-      state.inline = state.f = linkHref
-      return image
-    if ch is "[" and stream.match(/.*\](\(| ?\[)/, false)
-      state.linkText = true
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      return getType(state)
-    if ch is "]" and state.linkText
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      type = getType(state)
-      state.linkText = false
-      state.inline = state.f = linkHref
-      return type
-    if ch is "<" and stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)
-      state.f = state.inline = linkInline
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      type = getType(state)
-      if type
-        type += " "
-      else
-        type = ""
-      return type + linkinline
-    if ch is "<" and stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)
-      state.f = state.inline = linkInline
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      type = getType(state)
-      if type
-        type += " "
-      else
-        type = ""
-      return type + linkemail
-    if ch is "<" and stream.match(/^\w/, false)
-      unless stream.string.indexOf(">") is -1
-        atts = stream.string.substring(1, stream.string.indexOf(">"))
-        state.md_inside = true  if /markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)
-      stream.backUp 1
-      state.htmlState = CodeMirror.startState(htmlMode)
-      return switchBlock(stream, state, htmlBlock)
-    if ch is "<" and stream.match(/^\/\w*?>/)
-      state.md_inside = false
-      return "tag"
-    ignoreUnderscore = false
-    unless modeCfg.underscoresBreakWords
-      if ch is "_" and stream.peek() isnt "_" and stream.match(/(\w)/, false)
-        prevPos = stream.pos - 2
-        if prevPos >= 0
-          prevCh = stream.string.charAt(prevPos)
-          ignoreUnderscore = true  if prevCh isnt "_" and prevCh.match(/(\w)/, false)
-    if ch is "*" or (ch is "_" and not ignoreUnderscore)
-      if sol and stream.peek() is " "
+   match = stream.match /^>>/
+   if match
+    state.link = false
+    return operatorInline

-      # Do nothing, surrounded by newline and space
-      else if state.strong is ch and stream.eat(ch) # Remove STRONG
-        state.formatting = "strong"  if modeCfg.highlightFormatting
-        t = getType(state)
-        state.strong = false
-        return t
-      else if not state.strong and stream.eat(ch) # Add STRONG
-        state.strong = ch
-        state.formatting = "strong"  if modeCfg.highlightFormatting
-        return getType(state)
-      else if state.em is ch # Remove EM
-        state.formatting = "em"  if modeCfg.highlightFormatting
-        t = getType(state)
-        state.em = false
-        return t
-      else unless state.em # Add EM
-        state.em = ch
-        state.formatting = "em"  if modeCfg.highlightFormatting
-        return getType(state)
-    else if ch is " "
-      if stream.eat("*") or stream.eat("_") # Probably surrounded by spaces
-        if stream.peek() is " " # Surrounded by spaces, ignore
-          return getType(state)
-        else # Not surrounded by spaces, back up pointer
-          stream.backUp 1
-    if modeCfg.strikethrough
-      if ch is "~" and stream.eatWhile(ch)
-        if state.strikethrough # Remove strikethrough
-          state.formatting = "strikethrough"  if modeCfg.highlightFormatting
-          t = getType(state)
-          state.strikethrough = false
-          return t
-        else if stream.match(/^[^\s]/, false) # Add strikethrough
-          state.strikethrough = true
-          state.formatting = "strikethrough"  if modeCfg.highlightFormatting
-          return getType(state)
-      else if ch is " "
-        if stream.match(/^~~/, true) # Probably surrounded by space
-          if stream.peek() is " " # Surrounded by spaces, ignore
-            return getType(state)
-          else # Not surrounded by spaces, back up pointer
-            stream.backUp 2
-    if ch is " "
-      if stream.match(RegExp(" +$"), false)
-        state.trailingSpace++
-      else state.trailingSpaceNewLine = true  if state.trailingSpace
-    getType state
-  linkInline = (stream, state) ->
-    ch = stream.next()
-    if ch is ">"
-      state.f = state.inline = inlineNormal
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      type = getType(state)
-      if type
-        type += " "
-      else
-        type = ""
-      return type + linkinline
-    stream.match /^[^>]+/, true
-    linkinline
-  linkHref = (stream, state) ->

-    # Check if space, and return NULL if so (to avoid marking the space)
-    return null  if stream.eatSpace()
-    ch = stream.next()
-    if ch is "(" or ch is "["
-      state.f = state.inline = getLinkHrefInside((if ch is "(" then ")" else "]"))
-      state.formatting = "link-string"  if modeCfg.highlightFormatting
-      state.linkHref = true
-      return getType(state)
-    "error"
-  getLinkHrefInside = (endChar) ->
-    (stream, state) ->
-      ch = stream.next()
-      if ch is endChar
-        state.f = state.inline = inlineNormal
-        state.formatting = "link-string"  if modeCfg.highlightFormatting
-        returnState = getType(state)
-        state.linkHref = false
-        return returnState
-      stream.backUp 1  if stream.match(inlineRE(endChar), true)
-      state.linkHref = true
-      getType state
-  footnoteLink = (stream, state) ->
-    if stream.match(/^[^\]]*\]:/, false)
-      state.f = footnoteLinkInside
-      stream.next() # Consume [
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      state.linkText = true
-      return getType(state)
-    switchInline stream, state, inlineNormal
-  footnoteLinkInside = (stream, state) ->
-    if stream.match(/^\]:/, true)
-      state.f = state.inline = footnoteUrl
-      state.formatting = "link"  if modeCfg.highlightFormatting
-      returnType = getType(state)
-      state.linkText = false
-      return returnType
-    stream.match /^[^\]]+/, true
-    linktext
-  footnoteUrl = (stream, state) ->
-
-    # Check if space, and return NULL if so (to avoid marking the space)
-    return null  if stream.eatSpace()
-
-    # Match URL
-    stream.match /^[^\s]+/, true
-
-    # Check for link title
-    if stream.peek() is `undefined` # End of line, set flag to check next line
-      state.linkTitle = true
-    else # More content on line, check if link title
-      stream.match /^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true
-    state.f = state.inline = inlineNormal
-    linkhref
-  inlineRE = (endChar) ->
-    unless savedInlineRE[endChar]
-
-      # Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
-      endChar = (endChar + "").replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1")
-
-      # Match any non-endChar, escaped character, as well as the closing
-      # endChar.
-      savedInlineRE[endChar] = new RegExp("^(?:[^\\\\]|\\\\.)*?(" + endChar + ")")
-    savedInlineRE[endChar]
-  htmlFound = CodeMirror.modes.hasOwnProperty("xml")
-  htmlMode = "text/plain"
-  if htmlFound
-   htmlMode = name: "xml", htmlMode: true
-  htmlMode = CodeMirror.getMode cmCfg, htmlMode
-  modeCfg.highlightFormatting = false  if modeCfg.highlightFormatting is `undefined`
-  modeCfg.maxBlockquoteDepth = 0  if modeCfg.maxBlockquoteDepth is `undefined`
-  modeCfg.underscoresBreakWords = true  if modeCfg.underscoresBreakWords is `undefined`
-  modeCfg.fencedCodeBlocks = false  if modeCfg.fencedCodeBlocks is `undefined`
-  modeCfg.taskLists = false  if modeCfg.taskLists is `undefined`
-  modeCfg.strikethrough = false  if modeCfg.strikethrough is `undefined`
-  codeDepth = 0
-  header = "header"
-  code = "comment"
-  quote = "quote"
-  list1 = "variable-2"
-  list2 = "variable-3"
-  list3 = "keyword"
-  hr = "hr"
-  image = "tag"
-  formatting = "formatting"
-  linkinline = "link"
-  linkemail = "link"
-  linktext = "link"
-  linkhref = "string"
-  em = "em"
-  strong = "strong"
-  strikethrough = "strikethrough"
-  hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
-  ulRE = /^[*\-+]\s+/
-  olRE = /^[0-9]+\.\s+/
-  taskListRE = /^\[(x| )\](?=\s)/
-  atxHeaderRE = /^#+/
-  setextHeaderRE = /^(?:\={1,}|-{1,})$/
-  textRE = /^[^#!\[\]*_\\<>` "'(~]+/
-  savedInlineRE = []
-
-
-  operator = "keyword"
-  console.log cmCfg
-  htmlMode = CodeMirror.getMode cmCfg, name: "xml", htmlMode: true

   mode =
     startState: ->
      stack: []
      htmlState: null
+     start: true
+     bold: false
+     italics: false
+     subscript: false
+     superscript: false
+     code: false
+     link: false

+    blankLine: (state) ->
+     state.bold = false
+     state.italics = false
+     state.subscript = false
+     state.superscript = false
+     state.code = false
+     state.link = false

     token: (stream, state) ->
      if stream.sol()
-      return "" if stream.eatSpace()
+      state.start = true
+      s = stream.eatSpace()
+
+      return "" if s

      stack = state.stack

-     while stack.length > 0
-      console.log 'indent', stream.indentation()
-      if stack[stack.length - 1].indentation >= stream.indentation()
-       stack.pop()
-      else
-       break
+     if state.start
+      while stack.length > 0
+       console.log 'indent', stream.indentation()
+       if stack[stack.length - 1].indentation >= stream.indentation()
+        stack.pop()
+       else
+        break

-     match = stream.match /^<<</
-     if match
-      stack.push indentation: stream.indentation(), type: 'html'
-      stream.skipToEnd()
-      state.htmlState = CodeMirror.startState htmlMode
-      return operator
-
-     match = stream.match /^\+\+\+/
-     if match
-      stack.push indentation: stream.indentation(), type: 'special'
-      stream.skipToEnd()
-      return operator
-
-     match = stream.match /^>>>/
-     if match
-      stack.push indentation: stream.indentation(), type: 'sidenote'
-      stream.skipToEnd()
-      return "strong"
-
-     match = stream.match /^```/
-     if match
-      stack.push indentation: stream.indentation(), type: 'code'
-      stream.skipToEnd()
-      return operator
+      match = matchBlock stream, state
+      return match if match?

      types =
       sidenote: false
@@ -528,6 +122,8 @@
      for t in stack
       types[t.type] = true

+     l = ""
+
      if types.html
       l = htmlMode.token stream, state.htmlState
       l = "#{l}"
@@ -535,8 +131,20 @@
       stream.skipToEnd()
       l = "hr"
      else
-      stream.skipToEnd()
-      l = ""
+      match = matchInline stream, state
+      return match if match?
+
+      stream.next()
+      state.start = false
+
+      if state.bold
+       l += " strong"
+      if state.italics
+       l += " italics"
+      if state.link
+       l += " link"
+      if state.code
+       l += " meta"

      return l


Deal with test failures via error

Deal with test failures via error

--- src/runner.coffee
+++ src/runner.coffee
@@ -254,9 +254,10 @@
     sendWait = (data, cb) =>
       sendMessageAndWait @client, @currentGraphId, data.inputs, data.expect, cb
     common.asyncSeries sequence, sendWait, (err, actuals) ->
+      return callback err if err
       actuals.forEach (r, idx) ->
         sequence[idx].actual = r
-      return callback err, sequence
+      return callback null, sequence
     return

 # TODO: should this go into expectation?
@@ -310,7 +311,12 @@
       error: new Error "Test sequence length mismatch. Got #{inputs.length} inputs and #{expects.length} expectations"

   runner.runTest testcase, (err, results) ->
-    return callback err, null if err
+    if err
+      # Map error to a test failure
+      result =
+        passed: false
+        error: err
+      return callback null, result
     result = checkResults results
     if result.error
       result.passed = false


Add internal scene entity cache

Add internal scene entity cache

--- src/scenes.coffee
+++ src/scenes.coffee
@@ -134,7 +134,9 @@
     @background     = new Image()
     @background.src = opts.background || ''
     @entities       = new Kona.Store
+    @entityCache    = [] # Internal
     @next           = opts.next || null
+

     @loadEntities(opts.entities) if opts.entities?
     Kona.Scenes.scenes.push(@)
@@ -146,6 +148,7 @@
     entity.loadAnimations()
     @entities.add(entity.group, entity)
     Kona.Scenes.persistentEntities.push(entity) if entity.persistent
+    @expireEntityCache()

   # Internal: Initialize and construct the associated entities for a scene
@@ -186,6 +189,17 @@
       y += Kona.Tile.tileSize

+  # Internal: Return a cached Array[Kona.Entity] or compute it
+  getEntities: ->
+    if @entityCache.length
+      @entityCache
+    else
+      @entityCache = @entities.concat()
+
+  # Internal: Force the entity cache to be recomputed
+  expireEntityCache: -> @entityCache = []
+
+
   # Internal: Remove an entity from its group.
   # Prefer `entity.destroy()` instead of calling this directly.
   #
@@ -195,6 +209,7 @@
     list = @entities.for(entity.group)
     for ent, idx in list
       list.splice(idx, 1) if entity == ent
+      @expireEntityCache()

   # Internal: Render a scene and all of its entities onto the main canvas
@@ -212,7 +227,7 @@
   draw: ->
     Kona.Canvas.clear()
     Kona.Canvas.ctx.drawImage(@background, 0, 0)
-    ents = @entities.concat()
+    ents = @getEntities()
     for entity in ents
       if entity?
         entity.update() unless Kona.gamePaused


check every 24 hours

check every 24 hours

--- src/script.coffee
+++ src/script.coffee
@@ -35,7 +35,7 @@
     , (items) =>
       @options = items

-      if Date.now() > @options["lastChecked"] + (1000 * 60 * 60)
+      if Date.now() > @options["lastChecked"] + (1000 * 60 * 60 * 24)
         @update()
       else
         @highlight()


Add status message for when notifications are disabled

--- Web/js/classes/chat.coffee
+++ Web/js/classes/chat.coffee
@@ -59,8 +59,8 @@
 				@statusMessage 'Password saved.'
 		else if ! command.indexOf 'notify'
 			if command.length <= 7
-				res = false # Hi Coffeelint
-				# notifications.disableNotifications
+				# notifications.disableNotifications()
+				@statusMessage 'Notifications disabled!'
 			else
 				res = false
 				# res = notifications.setNotificationRegex message.substr 8


Verify path of loaded grammar

Verify path of loaded grammar

--- spec/highlights-spec.coffee
+++ spec/highlights-spec.coffee
@@ -38,14 +38,12 @@
     it "loads the grammars from a file-based npm module path", ->
       highlights = new Highlights()
       highlights.requireGrammarsSync(modulePath: require.resolve('atom-language-clojure/package.json'))
-      html = highlights.highlightSync(fileContents: '(def ^:dynamic chunk-size 17)', scopeName: 'source.clojure')
-      expect(html).toContain ''
+      expect(highlights.registry.grammarForScopeName('source.clojure').path).toBe path.resolve(__dirname, '..', 'node_modules', 'atom-language-clojure', 'grammars', 'clojure.cson')

     it "loads the grammars from a folder-based npm module path", ->
       highlights = new Highlights()
       highlights.requireGrammarsSync(modulePath: path.resolve(__dirname, '..', 'node_modules', 'atom-language-clojure'))
-      html = highlights.highlightSync(fileContents: '(def ^:dynamic chunk-size 17)', scopeName: 'source.clojure')
-      expect(html).toContain ''
+      expect(highlights.registry.grammarForScopeName('source.clojure').path).toBe path.resolve(__dirname, '..', 'node_modules', 'atom-language-clojure', 'grammars', 'clojure.cson')

     it "loads default grammars prior to loading grammar from module", ->
       highlights = new Highlights()


string_extensions

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -179,7 +179,9 @@
 Assumes the string is something like a file name and returns the
 contents of the string without the extension.

+

 "neat.png".witouthExtension() => "neat"
+

 @name withoutExtension
 @methodOf String#


Replace upperCase with shift-lowerCase

Replace upperCase with shift-lowerCase

--- keymaps/markdown-preview.cson
+++ keymaps/markdown-preview.cson
@@ -1,5 +1,5 @@
 'atom-workspace, atom-workspace atom-text-editor':
-  'ctrl-M': 'markdown-preview:toggle'
+  'ctrl-shift-m': 'markdown-preview:toggle'

 '.platform-darwin .markdown-preview':
   'cmd-+': 'markdown-preview:zoom-in'


error handling improved

error handling improved

--- client/landing/app/Applications/Account.kdapplication/views/emailnotifications.coffee
+++ client/landing/app/Applications/Account.kdapplication/views/emailnotifications.coffee
@@ -43,9 +43,14 @@
         stateValue = if state is 'ON' then on else off

         account.setEmailPreferences global: stateValue, (err)=>
-          @fallBackToOldState if err
+          if err
+            global.oldValue = globalValue
+            global.fallBackToOldState()
+            return new KDNotificationView
+              duration : 2000
+              title    : "Failed to turn #{state.toLowerCase()} the email notifications."

-        @emit 'GlobalStateChanged', state
+          @emit 'GlobalStateChanged', state

     for own flag, field of fields
       @addSubView field.formView = new KDFormView


removing unnecessary instance property.

removing unnecessary instance property.

--- client/landing/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
@@ -7,22 +7,21 @@

     currentGroup = KD.getSingleton("groupsController").getCurrentGroup()

-    @getContentGroupLinkPartial = (groupSlug, groupName)->
-      return "" if currentGroup?.slug is groupSlug
-      """
-      In #{groupName}
-      """
+    getContentGroupLinkPartial = (groupSlug, groupName)->
+      if currentGroup?.slug is groupSlug
+      then ""
+      else "In #{groupName}"

     @contentGroupLink = new KDCustomHTMLView
       tagName     : "span"
-      partial     : @getContentGroupLinkPartial(data.group, data.group)
+      partial     : getContentGroupLinkPartial(data.group, data.group)

     if currentGroup?.slug is data.group
-      @contentGroupLink.updatePartial @getContentGroupLinkPartial(currentGroup.slug, currentGroup.title)
+      @contentGroupLink.updatePartial getContentGroupLinkPartial(currentGroup.slug, currentGroup.title)
     else
       KD.remote.api.JGroup.one {slug:data.group}, (err, group)=>
         if not err and group
-          @contentGroupLink.updatePartial @getContentGroupLinkPartial(group.slug, group.title)
+          @contentGroupLink.updatePartial getContentGroupLinkPartial(group.slug, group.title)

     origin =
       constructorName  : data.originType


Titles translated

Titles translated

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -856,6 +856,9 @@
         "Ion Torpedoes":
             name: "Torpedos de Iones"
             text: """Ataque (Blanco Fijado): Gasta tu ficha de Blanco Fijado y descarta esta carta para efectuar este ataque.

Si este ataque impacta, el defensor y toda nave que esté a alcance 1 reciben 1 ficha de Iones cada una."""
+        "Bomb Loadout":
+            name: "Compartimento de Bombas"
+            text: """Solo ala-Y.

Tu barra de mejoras gana el icono %BOMB%."""
         "Bodyguard":
             name: "Guardaespaldas"
             text: """%SCUMONLY%

Al principio de la fase de Combate, puedes gastar 1 ficha de Concentración para elegir 1 nave aliada situada a alcance 1 cuyo piloto tenga una Habilidad más alta que la tuya. Hasta el final de la ronda, la puntuación de Agilidad de esa nave se incrementa en 1."""
@@ -1012,47 +1015,38 @@
         "Outrider":
             name: "Jinete del Espacio"
             text: """Solo YT-2400.

Mientras tu nave tenga equipada una mejora de %CANNON%, no puedes atacar con tu armamento principal y puedes atacar con armamentos secundarios %CANNON% contra naves enemigas fuera de tu arco de fuego."""
-        "Dauntless":
-            name: "Intrépido"
-            text: """Solo VT-49 Diezmador.

Después de que ejecutes una maniobra que te solape con otra nave, puedes realizar 1 acción gratuita. Luego recibes 1 ficha de Tensión."""
-        "Virago":
-            text: """Solo Víbora Estelar.

Tu barra de mejoras gana los iconos %SYSTEM% y %ILLICIT%.

Esta mejora no puede equiparse en naves con pilotos de Habilidad 3 o inferior."""
-        '"Heavy Scyk" Interceptor (Cannon)':
-            name: 'Interceptor "Scyk Pesado" (Cañón)'
-            text: """Solo Interceptor M3-A.

Tu barra de mejoras gana el icono %CANNON%."""
-        '"Heavy Scyk" Interceptor (Torpedo)':
-            name: 'Interceptor "Scyk Pesado" (Torpedo)'
-            text: """Solo Interceptor M3-A.

Tu barra de mejoras gana el icono %TORPEDO%."""
-        '"Heavy Scyk" Interceptor (Missile)':
-            name: 'Interceptor "Scyk Pesado" (Misil)'
-            text: """Solo Interceptor M3-A.

Tu barra de mejoras gana el icono %MISSILE%."""
-        "IG-2000":
-            text: """Solo Agresor.

Tu piloto tiene la misma capacidad especial que cualquier otra nave aliada equipada con la carta de Mejora IG-2000 (además de su propia capacidad especial)."""
         "Andrasta":
             name: "Andrasta"
             text: """Solo Firespray-31.

Tu barra de mejoras gana 2 iconos %BOMB% adicionales."""
         "TIE/x1":
             text: """TIE Advanced only.%LINEBREAK%Your upgrade bar gains the %SYSTEM% upgrade icon.%LINEBREAK%If you equip a %SYSTEM% upgrade, its squad point cost is reduced by 4 (to a minimum of 0)."""
         "BTL-A4 Y-Wing":
-            name: "BTL-A4 Y-Wing"
+            name: "BTL-A4 Ala-Y"
+            text: """Solo Ala-Y.

No puedes atacar naves que estén fuera de tu arco de fuego. Después de que efectúes un ataque con tu armamento principal, puedes realizar inmediatamente un ataque con arma secundaria %TURRET%."""
             ship: "Ala-Y"
         "IG-2000":
             name: "IG-2000"
+            text: """Solo Agresor.

Tu piloto tiene la misma capacidad especial que cualquier otra nave aliada equipada con la carta de Mejora IG-2000 (además de su propia capacidad especial)."""
             ship: "Agresor"
         "Virago":
             name: "Virago"
+            text: """Solo Víbora Estelar.

Tu barra de mejoras gana los iconos %SYSTEM% y %ILLICIT%.

Esta mejora no puede equiparse en naves con pilotos de Habilidad 3 o inferior."""
             ship: 'Víbora Estelar'
         '"Heavy Scyk" Interceptor (Cannon)':
-            name: '"Heavy Scyk" Interceptor (Cannon)'
+            name: 'Interceptor "Scyk Pesado" (Cañón)'
+            text: """Solo Interceptor M3-A.

Tu barra de mejoras gana el icono %CANNON%."""
             ship: 'Interceptor M3-A'
         '"Heavy Scyk" Interceptor (Missile)':
-            name: '"Heavy Scyk" Interceptor (Missile)'
+            name: 'Interceptor "Scyk Pesado" (Misil)'
+            text: """Solo Interceptor M3-A.

Tu barra de mejoras gana el icono %MISSILE%."""
             ship: 'Interceptor M3-A'
         '"Heavy Scyk" Interceptor (Torpedo)':
-            name: '"Heavy Scyk" Interceptor (Torpedo)'
+            name: 'Interceptor "Scyk Pesado" (Torpedo)'
+            text: """Solo Interceptor M3-A.

Tu barra de mejoras gana el icono %TORPEDO%."""
             ship: 'Interceptor M3-A'
         "Dauntless":
-            name: "Dauntless"
+            name: "Intrépido"
+            text: """Solo VT-49 Diezmador.

Después de que ejecutes una maniobra que te solape con otra nave, puedes realizar 1 acción gratuita. Luego recibes 1 ficha de Tensión."""
             ship: 'VT-49 Diezmador'

     exportObj.setupCardData basic_cards, pilot_translations, upgrade_translations, modification_translations, title_translations


Ticket zoom: increased double-click/metaToggle timeout (fixes #2586)

--- app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee
@@ -291,7 +291,7 @@
   toggleMetaWithDelay: (e) =>
     # allow double click select
     # by adding a delay to the toggle
-    delay = 120
+    delay = 300

     if @lastClick and +new Date - @lastClick < delay
       clearTimeout(@toggleMetaTimeout)


Since the server now binds, we connect.

Since the server now binds, we connect.

--- src/server.coffee
+++ src/server.coffee
@@ -25,7 +25,7 @@
 zmq_pusher = zmq.socket("push")
 zmq_subscriber.connect config.zmq.sub
 zmq_subscriber.subscribe ""
-zmq_pusher.bind config.zmq.push
+zmq_pusher.connect config.zmq.push
 console.log "cli subcribed to #{config.zmq.sub}"
 console.log "cli pushing to #{config.zmq.push}"


Don't assume that save is synchronous in specs

--- spec/whitespace-spec.coffee
+++ spec/whitespace-spec.coffee
@@ -347,9 +347,12 @@
       buffer.setText("foo   \nbar\t   \n\nbaz")

     it "saves the file without removing any trailing whitespace", ->
-      atom.commands.dispatch(workspaceElement, 'whitespace:save-with-trailing-whitespace')
-      expect(buffer.getText()).toBe "foo   \nbar\t   \n\nbaz"
-      expect(buffer.isModified()).toBe false
+      waitsFor (done) ->
+        buffer.onDidSave ->
+          expect(buffer.getText()).toBe "foo   \nbar\t   \n\nbaz"
+          expect(buffer.isModified()).toBe false
+          done()
+        atom.commands.dispatch(workspaceElement, 'whitespace:save-with-trailing-whitespace')

   describe "when the 'whitespace:save-without-trailing-whitespace' command is run", ->
     beforeEach ->
@@ -358,9 +361,12 @@
       buffer.setText("foo   \nbar\t   \n\nbaz")

     it "saves the file and removes any trailing whitespace", ->
-      atom.commands.dispatch(workspaceElement, 'whitespace:save-without-trailing-whitespace')
-      expect(buffer.getText()).toBe "foo\nbar\n\nbaz"
-      expect(buffer.isModified()).toBe false
+      waitsFor (done) ->
+        buffer.onDidSave ->
+          expect(buffer.getText()).toBe "foo\nbar\n\nbaz"
+          expect(buffer.isModified()).toBe false
+          done()
+        atom.commands.dispatch(workspaceElement, 'whitespace:save-without-trailing-whitespace')

   describe "when the 'whitespace:convert-tabs-to-spaces' command is run", ->
     it "removes leading \\t characters and replaces them with spaces using the configured tab length", ->


improve logging on change email in newsletter

--- services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
+++ services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
@@ -38,6 +38,7 @@
 		options = buildOptions({email:oldEmail})
 		delete options.body.status
 		options.body.email_address = newEmail
+		logger.log {oldEmail, newEmail, options}, "changing email in newsletter"
 		mailchimp.request options, (err)->
 			if err? and err?.message?.indexOf("merge fields were invalid") != -1
 				logger.log {oldEmail, newEmail}, "unable to change email in newsletter, user has never subscribed"
@@ -75,6 +76,5 @@
 			LNAME: user.last_name
 			MONGO_ID:user._id

-	console.log opts
 	return opts


:memo: Add change event handling comments

--- lib/symbol-provider.coffee
+++ lib/symbol-provider.coffee
@@ -88,6 +88,23 @@
     # Should we disqualify TextEditors with the Grammar text.plain.null-grammar?
     return paneItem instanceof TextEditor

+  # Buffer change handling. The goal is to remove symbols from the `symbolList`
+  # that were removed from the buffer, and add symbols that were added.
+  #
+  # 1. Use onWillChange, and onDidChange events to build an XOR list of tokens
+  #    to add and remove from the `symbolList`. This XOR list is
+  #    `realtimeUpdateTokenStore`
+  # 2. Trigger an async update to the `symbolList`
+  #
+  # In `updateChangedTokens` will add and remove necessary tokens from `symbolList`
+  #
+  # Why do it this way?
+  #
+  # * Reading of the tokens must happen synchonously in the event handlers as
+  #   thats the only time the buffer will have the tokens matching the change events.
+  # * The slow part is the token scope selector matching to bucket tokens
+  #   by type. So we try to minimize the amount of selector matching that
+  #   happens, and group as much as possible into one event.
   bufferWillChange: ({oldRange}) =>
     tokenizedLines = @getTokenizedLines(@editor)[oldRange.start.row..oldRange.end.row]
     bufferRowBase = oldRange.start.row
@@ -95,7 +112,7 @@
       bufferRow = bufferRowBase + bufferRowIndex
       for token in tokens
         @realtimeUpdateTokenStore.remove(token, @editor.getPath(), bufferRow)
-    @debouncedUpdateChangedTokens()
+    return

   bufferDidChange: ({newRange}) =>
     tokenizedLines = @getTokenizedLines(@editor)[newRange.start.row..newRange.end.row]


fix q function return value

fix q function return value

--- ops.coffee
+++ ops.coffee
@@ -289,4 +289,4 @@
   for t in targets
     unless $.type(t) is 'string'
       throw new TypeError "#{t} is not a string"
-  new lead.type.q targets.map(String)...
+  value new lead.type.q targets.map(String)...


changed zipcode to 94107

changed zipcode to 94107

--- client/landing/app/Applications/Home.kdapplication/ContentDisplays/AboutView.coffee
+++ client/landing/app/Applications/Home.kdapplication/ContentDisplays/AboutView.coffee
@@ -55,7 +55,7 @@
               Koding, Inc.

                 358 Brannan

-                San Francisco, CA 94110
+                San Francisco, CA 94107

           </address>


kite2: DRY

kite2: DRY

--- client/Main/kite/kite2.coffee
+++ client/Main/kite/kite2.coffee
@@ -15,10 +15,10 @@
     e

   tell2: (method, params = {}) ->
+    { correlationName, kiteName, timeout: classTimeout } = @getOptions()
+
     # #tell2 is wrapping #tell with a promise-based api
     new Promise (resolve, reject) =>
-
-      { correlationName, kiteName, timeout: classTimeout } = @getOptions()

       options = {
         method
@@ -27,16 +27,10 @@
         withArgs: params
       }

-      # handle timeout:
-      timeOk = yes
-      if params?.timeout not in [null, Infinity]
-        timeout = params?.timeout ? classTimeout ? 5000
-        KD.utils.wait timeout, ->
-          timeOk = no
-          reject new Error "Request timeout exceeded (#{ timeout }ms)"
-
       callback = (err, restResponse...) ->
         return reject createProperError err   if err?
-        return resolve restResponse...        if timeOk
+        return resolve restResponse...

       @tell options, callback
+
+    .timeout classTimeout ? 5000


Add scale option update transitions

Add scale option update transitions

--- client/landing/app/Applications/Demos.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Demos.kdapplication/AppView.coffee
@@ -30,6 +30,8 @@
   constructor:(options={}, data)->

     options.cssClass = KD.utils.curry 'kd-slide', options.cssClass
+    options.scale    = 0.8
+
     super options, data

     @pages     = []
@@ -87,9 +89,9 @@

     pref = {}
     pref[key] = "#{direction}%"
-
+    scale = @getOption 'scale'
     props = ['webkitTransform', 'MozTransform', 'transform']
-    pref[prop] = "scale(.8)"  for prop in props
+    pref[prop] = "scale(#{scale})"  for prop in props

     if coord is X_COORD
       currentPage = pages[current][@_coordsY[current]]
@@ -115,7 +117,7 @@
     KD.getSingleton("mainView").enableFullscreen()

     @addSubView @slider = new Slider
-      # direction : Slider.directions.topToBottom
+      directions : Slider.directions.topToBottom

     @slider.addPage page1 = new Page
       content  : 'Page 1'


landing: remove useless code

landing: remove useless code

--- client/landing/site.landing/coffee/team/tabs/teamrecovertab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamrecovertab.coffee
@@ -60,7 +60,6 @@
         @form.button.hideLoader()
       success     : =>
         @form.reset()
-        {entryPoint} = kd.config
         new kd.NotificationView
           cssClass : 'recoverConfirmation'
           title    : 'Check your email'


OX-684 Fix mouse

OX-684 Fix mouse

--- app/assets/javascripts/oxalis/controller/celltracing_controller.coffee
+++ app/assets/javascripts/oxalis/controller/celltracing_controller.coffee
@@ -96,7 +96,7 @@
     scaleFactor = @view.scaleFactor
     camera      = @view.getCameras()[plane]
     # vector with direction from camera position to click position
-    vector = new THREE.Vector3((position.x / (384 * scaleFactor) ) * 2 - 1, - (position.x / (384 * scaleFactor)) * 2 + 1, 0.5)
+    vector = new THREE.Vector3((position.x / (384 * scaleFactor) ) * 2 - 1, - (position.y / (384 * scaleFactor)) * 2 + 1, 0.5)

     # create a ray with the direction of this vector, set ray threshold depending on the zoom of the 3D-view
     projector = new THREE.Projector()


Fixed manual syncing

Fixed manual syncing

--- lib/client.coffee
+++ lib/client.coffee
@@ -79,11 +79,17 @@
     if files
       getFiles( files )
     else
+      @notification.addInfo("Syncing...")
       @socket.emit 'client:getFiles'

       # Handle syncing
       @socket.once 'serverFiles', (data) =>
-        getFiles( data )
+        projectCount = Object.keys( data ).length
+        if projectCount > 1
+          getFiles( data )
+        else
+          getFiles( data[ Object.keys( data )[0] ].content )
+


swallow extra error throw

swallow extra error throw

--- lib/utils/multipart.coffee
+++ lib/utils/multipart.coffee
@@ -108,11 +108,15 @@
     parser.write(chunk)
     callback()
   flush = (callback) ->
-    err = parser.end()
-    if err
-      handleError(new errors.RetsProcessingError('getObject', "Unexpected end of data: #{errors.getErrorMessage(err)}", headerInfo))
-    flushed = true
-    handleEnd()
+    try
+      err = parser.end()
+      if err
+        handleError(new errors.RetsProcessingError('getObject', "Unexpected end of data: #{errors.getErrorMessage(err)}", headerInfo))
+      flushed = true
+      handleEnd()
+    catch err2
+      console.log('uncaught error is now caught: '+errors.getErrorMessage(err)+'\n'+(err2.stack || errors.getErrorMessage(err2)))
+      # I don't know how the error is getting thrown, but it's already been handled by this point, so we can swallow it
   stream.pipe(through2(interceptor, flush))
   resolve(objectStream)


prepared subview

prepared subview

--- app/assets/javascripts/admin/views/task/task_create_bulk_import_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_bulk_import_view.coffee
@@ -0,0 +1,25 @@
+### define
+underscore : _
+backbone.marionette : marionette
+routes : routes
+###
+
+class TaskCreateBulkImportView extends Backbone.Marionette.ItemView
+
+  id : "create-bulk-import"
+  template : _.template("""
+    <!-- PUT BULK IMPORT FROM HERE // disable auto-submission -->
+  """)
+
+  #events :
+    # put submit event here
+
+
+  ###*
+    * Submit form data as json.
+    *
+    * @method submit
+    ###
+  submit : ->
+
+    # trigger ajax


Update _media_manager.js.coffee

Update _media_manager.js.coffee

Modal close button is disabled at the same time that New Folder button.

--- app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js.coffee
+++ app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js.coffee
@@ -134,7 +134,7 @@
   media_panel.on("click", "a.add_folder", ->
     content = $("<form>
"+I18n('button.folder')+":
<input name='folder' class='form-control required' placeholder='Folder name..'><button class='btn btn-primary' type='submit'>"+I18n('button.create')+"</button>

 </form>")
     callback = (modal)->
-      btn = modal.find("button")
+      btn = modal.find(".btn-primary")
       input = modal.find("input").keyup(->
         if $(this).val()
           btn.removeAttr("disabled")
@@ -221,4 +221,4 @@
       if args["selected"]
         window["callback_media_uploader"] = args["selected"]
       modal.css("z-index", args["zindex"] || 99999).children(".modal-dialog").css("width", "90%")
-    })+    })


Cleanup

Cleanup

--- client/lib/setup/index.coffee
+++ client/lib/setup/index.coffee
@@ -44,15 +44,16 @@
   app.use bodyParser.urlencoded limit:'5mb', extended: true
   app.use morgan if NODE_ENV is 'development' then 'dev' else (tokens, req, res) ->
     status = tokens.status(req, res)
+    url = tokens.url(req, res)
     chalk.yellow('CLIENT:') +
       ' ' + chalk.blue(tokens.method(req, res)) +
       ' ' + (
               if status >= 500
-                chalk.red(tokens.url(req, res)) + ' ' + chalk.red(status)
+                chalk.red(url + ' ' + status)
               else if status >= 400
-                chalk.yellow(tokens.url(req, res)) + ' ' + chalk.yellow(status)
+                chalk.yellow(url + ' ' + status)
               else
-                chalk.green(tokens.url(req, res)) + ' ' + chalk.green(status)
+                chalk.green(url + ' ' + status)
             ) +
       ' ' + chalk.cyan(tokens['response-time'](req, res) + 'ms') +
       ' ' + chalk.white(tokens['remote-addr'](req, res)) +


adds comment explaining why not using toggle method as others

--- src/ui/views/trayicon.coffee
+++ src/ui/views/trayicon.coffee
@@ -50,6 +50,8 @@
           label: 'Show pop-up notifications'
           type: "checkbox"
           checked: viewstate.showPopUpNotifications
+          # usage of already existing method and implements same logic
+          #  as other toggle... methods
           click: -> action 'showpopupnotifications',
               !viewstate.showPopUpNotifications
         }


use getRange method to optain range. fixes #109

--- lib/atom-ternjs-helper.coffee
+++ lib/atom-ternjs-helper.coffee
@@ -43,7 +43,7 @@
   markerCheckpointBack: ->
     return unless @checkpointsDefinition.length
     checkpoint = @checkpointsDefinition.pop()
-    @openFileAndGoToPosition(checkpoint.marker.range.start, checkpoint.editor.getURI())
+    @openFileAndGoToPosition(checkpoint.marker.getRange().start, checkpoint.editor.getURI())

   setMarkerCheckpoint: ->
     editor = atom.workspace.getActiveTextEditor()


fixed issue with element focus

fixed issue with element focus

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -151,7 +151,7 @@
     treeViewEl.classList.remove 'autohide-unfolded'
     treeViewEl.style.transitionDelay = "#{transitionDelay}s"
     @applyHiddenWidth()
-    treeView.unfocus()
+    treeViewEl.blur()
     @enableHoverEvents()

   toggleVisible: ->


remove rev from gulpfile

remove rev from gulpfile

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -26,7 +26,6 @@
 plumber      = require "gulp-plumber"
 rename       = require "gulp-rename"
 replace      = require 'gulp-replace'
-rev          = require 'gulp-rev'
 sass         = require "gulp-sass"
 sourcemaps   = require "gulp-sourcemaps"
 shell        = require "gulp-shell"


Make position an integer, and keep it smaller than new Date().valueOf()

--- client/standup/standup.coffee
+++ client/standup/standup.coffee
@@ -27,7 +27,8 @@
       group = Session.get "currentGroupId"
       standupMembers = StandupMembers.find({group: Session.get("currentGroupId")})
       standupMembers.forEach (member) ->
-        StandupMembers.update({_id: member._id}, {$set: {position: Math.random(), selected: false}})
+        randomNumber = Math.floor(Math.random() * 1000000)
+        StandupMembers.update({_id: member._id}, {$set: {position: randomNumber, selected: false}})

     selectUnselected: (direction, sort) ->
       toBeSelected = @findUnselected direction: direction, sort: sort


Fix bug where sometimes Flow Router would run notFound too soon

- Leaving the page would stop data, which would run notFound handler

--- client/views/blog/blog.coffee
+++ client/views/blog/blog.coffee
@@ -70,7 +70,7 @@
       notFound = Blog.Router.getNotFoundTemplate()
       if notFound
         return notFound
-      else
+      else if Blog.Router.getParam('slug')
         Blog.Router.notFound()


Additional "often confused with" text

Additional "often confused with" text

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -608,6 +608,9 @@
       '''

     buffalo:
+      cattle: '''
+        Cattle are less heavy than buffalo, and have a more pronounced shoulder hump. The horns of cattle don't fuse in the middle and cattle are often more variable in color.
+      '''
       wildebeest: '''
         A wildebeest is skinnier than a buffalo, with a large head and long beard.  Its horns don’t fuse together in the center.
       '''
@@ -621,6 +624,14 @@
         Lions are larger and more muscular than caracals. They don't have large pointed ears. Their tails are smooth with a tuft of dark fur at the end.
       '''

+    cattle:
+      buffalo: '''
+        Buffalo are stockier than cattle and lack the prominent shoulder hump. The horns of cattle don't join together in the middle.
+      '''
+      wildebeest: '''
+        The horns of cattle are shorter and don't extend as far back as those of wildebeest. While cattle have a prominent shoulder hump, they don't have the sloping back of wildebeest and lack a chin-beard.
+      '''
+
     cheetah:
       leopard: '''
         Leopards are covered with black rosettes, not spots. They don't have black facial lines like a cheetah.
@@ -636,14 +647,14 @@
       '''

     dikDik:
-      reedbuck: '''
-        A reedbuck is bigger than a dik dik. Their undersides are white, not gray, and they lack the ‘tear drop’ beneath the eye.
-      '''
+      reedbuck: 'A reedbuck is bigger than a dik dik. Their undersides are white, not gray, and they lack the ‘tear drop’ beneath the eye.'
+      steenbok: 'Steenbok have larger ears than dik dik and lack the dark suborbital gland. They are slightly bigger in size.'
+      duiker: 'Dik dik have more petite, tapering noses that lack the dark muzzle stripe of a duiker. Dik dik have more prominent tufts of hair between their ears and a rounded posture.'

     duiker:
-      reedbuck: ''
-      dikDik: ''
-      steenbok: ''
+      reedbuck: 'Reedbuck lack the dark muzzle and forelegs of a duiker. The horns of a male duiker are straighter and smoother than those of a male reedbuck.'
+      dikDik: 'Dik dik are smaller and less stocky than a duiker, lacking the prominently dark muzzle and foreleg stripes.'
+      steenbok: 'Steenbok are more petite than duiker. The black facial glands of a steenbok do not extend to cover as much of the face as the dark muzzle stripe of the duiker.'

     gazelleGrants:
       gazelleThomsons: '''
@@ -757,8 +768,8 @@
       '''

     steenbok:
-      duiker: ''
-      dikDik: ''
+      duiker: 'A duiker is larger than a steenbok, with a more prominent black facial stripe and dark forelimbs.'
+      dikDik: 'Dik dik have a more rounded back and hunched posture than a steenbok. Dik dik have a more prominent tuft of hair between their ears.'

     topi:
       hartebeest: '''
@@ -777,6 +788,9 @@
     wildebeest:
       buffalo: '''
         The buffalo is larger and stockier. Horns tend to be more fused together at the center.
+      '''
+      cattle: '''
+        Wildebeest have more pronounced, curving horns than cattle. Cattle have a less bulbous muzzle and they lack the wildebeest's chin-beard.
       '''

     zorilla:


fixed indent

fixed indent

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -926,13 +926,13 @@
           and to the application of this knowledge to benefit the people of the state, the nation, and the world.
         '''

-        natgeo:
-          name: 'National Geographic'
-          url: 'http://www.nationalgeographic.com'
-          image: 'images/about/organizations/natgeo.jpg'
-          description: '''
-            National Geographic is a global nonprofit membership organization driven by a passionate belief in the power of science, exploration and storytelling to change the world. Working to inspire, illuminate and teach, National Geographic reaches more than 700 million people a month through its media platforms, products, events and experiences.
-          '''
+      natgeo:
+        name: 'National Geographic'
+        url: 'http://www.nationalgeographic.com'
+        image: 'images/about/organizations/natgeo.jpg'
+        description: '''
+          National Geographic is a global nonprofit membership organization driven by a passionate belief in the power of science, exploration and storytelling to change the world. Working to inspire, illuminate and teach, National Geographic reaches more than 700 million people a month through its media platforms, products, events and experiences.
+        '''

       mnZoo:
         name: 'Minnesota Zoo'


Add keyboard shortcuts from list and grid mode (closes #267)

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -149,6 +149,12 @@
   km 'right', -> mediator.trigger 'lightbox:slide:next'
   km 'left',  -> mediator.trigger 'lightbox:slide:prev'
   km 'esc',   -> mediator.trigger 'lightbox:close'
+  km 'l',     ->
+    mediator.shared.state.set view_mode: 'list'
+    window.location.reload()
+  km 'g',     ->
+    mediator.shared.state.set view_mode: 'grid'
+    window.location.reload()

   initNightMode()


README update

README update


refactor getMeshFromString

refactor getMeshFromString

--- app/vis.coffee
+++ app/vis.coffee
@@ -95,15 +95,22 @@
                 obj.prev_connector_index = prev_idx
             obj

+    getMeshFromString: (string) =>
+        string_geom = @getTextGeometry(string)
+        string_mesh = @getTextMesh(string_geom)
+        string_mesh.scale.multiplyScalar(-1)
+        string_mesh
+
     getLineObject: (_line, index) =>
         line_geometry = @getTextGeometry(_line.line)
         line_layout = line_geometry.layout
         glyph_positions = line_layout.glyphs.map (g) -> g.position

         letterObjects = _line.line.split("").map (letter, index) =>
-            letter_geom = @getTextGeometry(letter)
-            letter_mesh = @getTextMesh(letter_geom)
-            letter_mesh.scale.multiplyScalar(-1)
+            # letter_geom = @getTextGeometry(letter)
+            # letter_mesh = @getTextMesh(letter_geom)
+            # letter_mesh.scale.multiplyScalar(-1)
+            letter_mesh = @getMeshFromString(letter)
             letter_mesh.position.x = - glyph_positions[index][0]
             letter_mesh


render_list() flattens nested element arrays.

--- client/render.coffee
+++ client/render.coffee
@@ -4,4 +4,4 @@
   with_mixin {field: expand_type field}, @[field.type]

 @render_list = ->
-  with_mixin {xml: ($ xml)}, record for xml in records
+  ensure_element_array (with_mixin {xml: ($ xml)}, record for xml in records)


Fixes text for crossfire formation upgrade card

--- coffeescripts/cards-en.coffee
+++ coffeescripts/cards-en.coffee
@@ -1264,7 +1264,7 @@
         'Os-1 Arsenal Loadout':
             text: '''Alpha-class Star Wing only.%LINEBREAK%Your upgrade bar gains the %TORPEDO% and %MISSILE% icons.%LINEBREAK%You may perform attacks with %TORPEDO% and %MISSILE% secondary weapons against ships you have locked even while you have a weapons disabled token.'''
         'Crossfire Formation':
-            text: '''B/SF-17 Bomber only.%LINEBREAK%When defending, if there is at least 1 other friendly Resistance ship at Range 1-2 of the attacker with the Crossfire Formation Upgrade card, you may add 1 %FOCUS% result to your roll.'''
+            text: '''B/SF-17 Bomber only.%LINEBREAK%When defending, if there is at least 1 other friendly Resistance ship at Range 1-2 of the attacker, you may add 1 %FOCUS% result to your roll.'''

     condition_translations =
         '''I'll Show You the Dark Side''':


Fix typo; add Partisan Renegade.

Fix typo; add Partisan Renegade.

--- coffeescripts/manifest.coffee
+++ coffeescripts/manifest.coffee
@@ -4589,6 +4589,11 @@
             count: 1
         }
         {
+            name: 'Partisan Renegade'
+            type: 'pilot'
+            count: 1
+        }
+        {
             name: 'Benthic Two-Tubes'
             type: 'pilot'
             count: 1
@@ -4609,7 +4614,7 @@
             count: 1
         }
         {
-            name: 'Cavern Angel Zealot'
+            name: 'Cavern Angels Zealot'
             type: 'pilot'
             count: 3
         }


Change max{X,Y,Z} to width,height,depth in bulk task creation #1417

--- app/assets/javascripts/admin/views/task/task_create_subviews/task_create_bulk_import_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_subviews/task_create_bulk_import_view.coffee
@@ -13,7 +13,7 @@

         One line for each task. The values are seperated by ','. Format:

-	dataSet, taskTypeId, experienceDomain, minExperience, x, y, z, rotX, rotY, rotZ, priority, instances, team, minX, minY, minZ, maxX, maxY, maxZ, project

+	dataSet, taskTypeId, experienceDomain, minExperience, x, y, z, rotX, rotY, rotZ, priority, instances, team, minX, minY, minZ, width, height, depth, project

         <form action="" method="POST" class="form-horizontal" onSubmit="return false;">

@@ -173,9 +173,9 @@
     minX = parseInt(words[13])
     minY = parseInt(words[14])
     minZ = parseInt(words[15])
-    maxX = parseInt(words[16])
-    maxY = parseInt(words[17])
-    maxZ = parseInt(words[18])
+    width = parseInt(words[16])
+    height = parseInt(words[17])
+    depth = parseInt(words[18])

     projectName = ""
     if words[19]
@@ -197,9 +197,9 @@
       editRotation : [rotX, rotY, rotZ]
       boundingBox :
         topLeft : [minX, minY, minZ]
-        width : maxX
-        height : maxY
-        depth : maxZ
+        width : width
+        height : height
+        depth : depth
       projectName,
       isForAnonymous : false
     }


fix merge conflicts

fix merge conflicts

--- test/cases/support/stringTest.coffee
+++ test/cases/support/stringTest.coffee
@@ -40,5 +40,4 @@
       assert.equal support.singularize("businesses"), "business"
       assert.equal support.singularize("people"), "person"
       assert.equal support.singularize("person"), "person"
-
->>>>>>> origin/development
+


no need to create Object_DefineProperty every time.

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -1745,20 +1745,22 @@

         constructor: ->
                 super
-
+
+        Object_defineProperty = {
+                type: MemberExpression,
+                computed: false,
+                object: create_identifier("Object"),
+                property: create_identifier("defineProperty")
+        }
+
         create_lhs = (specifiers) ->
                 throw new Error("internal compiler error, only form \"import blah from 'blah'\" supported") if specifiers.length isnt 1
                 specifiers[0].id

         define_export_property = (exported_id, local_id = exported_id) ->
+                # return esprima.parse("Object.defineProperty(exports, '#{exported_id.name}', { get: function() { return #{local_id.name}; } });");
+
                 exports_id = create_identifier("exports") # XXX as with compiler.coffee, this 'exports' should be '%exports' if the module has ES6 module declarations
-
-                Object_defineProperty = {
-                        type: MemberExpression,
-                        computed: false,
-                        object: create_identifier("Object"),
-                        property: create_identifier("defineProperty")
-                }

                 getter = {
                         type: FunctionExpression,
@@ -1769,6 +1771,8 @@
                                 body: [
                                         { type: ReturnStatement, argument: local_id }
                                 ]
+
+
                 }

                 property_literal = {


remove some unused identifiers

remove some unused identifiers

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -102,10 +102,7 @@
 setSlot_id              = b.identifier "%setSlot"
 slot_id                 = b.identifier "%slot"
 invokeClosure_id        = b.identifier "%invokeClosure"
-setLocal_id             = b.identifier "%setLocal"
-setGlobal_id            = b.identifier "%setGlobal"
 getLocal_id             = b.identifier "%getLocal"
-getGlobal_id            = b.identifier "%getGlobal"
 getArg_id               = b.identifier "%getArg"
 getArgumentsObject_id   = b.identifier "%getArgumentsObject"
 moduleGetSlot_id        = b.identifier "%moduleGetSlot"


a little cleanup

a little cleanup

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -282,8 +282,8 @@

         # TODO: move this into the @visit method
         free: (exp) ->
-                if not exp?
-                        return new Set
+                return new Set if not exp?
+
                 switch exp.type
                         when syntax.Program
                                 decls = @decl_names @collect_decls exp.body
@@ -293,11 +293,9 @@
                         when syntax.FunctionDeclaration
                                 exp.ejs_free_vars = (@free exp.body).subtract (@param_names exp.params)
                                 exp.ejs_decls = exp.body.ejs_decls.union (@param_names exp.params)
-                                exp.ejs_free_vars
                         when syntax.FunctionExpression
                                 exp.ejs_free_vars = (@free exp.body).subtract (@param_names exp.params)
                                 exp.ejs_decls = exp.body.ejs_decls.union (@param_names exp.params)
-                                exp.ejs_free_vars
                         when syntax.LabeledStatement      then exp.ejs_free_vars = @free exp.body
                         when syntax.BlockStatement        then exp.ejs_free_vars = @free_blocklike exp, exp.body
                         when syntax.TryStatement          then exp.ejs_free_vars = Set.union.apply null, [(@free exp.block)].concat (map @call_free, exp.handlers)
@@ -305,7 +303,6 @@
                                 param_set = if exp.param?.name? then new Set [exp.param.name] else new Set
                                 exp.ejs_free_vars = (@free exp.body).subtract param_set
                                 exp.ejs_decls = exp.body.ejs_decls.union param_set
-                                exp.ejs_free_vars
                         when syntax.VariableDeclaration   then exp.ejs_free_vars = Set.union.apply null, (map @call_free, exp.declarations)
                         when syntax.VariableDeclarator    then exp.ejs_free_vars = @free exp.init
                         when syntax.ExpressionStatement   then exp.ejs_free_vars = @free exp.expression


make blank slate dismissal persistent

make blank slate dismissal persistent

--- web/js/views/blank_slate_view.coffee
+++ web/js/views/blank_slate_view.coffee
@@ -23,6 +23,7 @@
       false

     onGoneHome: =>
+      return if @isDismissed()
       @$el.show()
       @$el.html(template(strings: strings.blankSlate))

@@ -32,3 +33,8 @@

     dismiss: =>
       @$el.remove()
+      expiresAt = new Date(new Date().getTime() + 20 * 365 * 24 * 60 * 60 * 1000)
+      document.cookie = "blankSlateDismissed=true; expires=#{expiresAt.toUTCString()}; path: /"
+
+    isDismissed: =>
+      /blankSlateDismissed/.test(document.cookie)


test showing enroll-or-login screen

test showing enroll-or-login screen

--- test/course/registration.spec.coffee
+++ test/course/registration.spec.coffee
@@ -4,6 +4,7 @@
 STATUS = require '../../auth/status/GET'
 User = require 'user/model'
 Registration = require 'course/registration'
+EnrollOrLogin = require 'course/enroll-or-login'
 NewCourseRegistration = require 'course/new-registration'
 ModifyCourseRegistration = require 'course/modify-registration'

@@ -12,9 +13,16 @@
   beforeEach ->
     @props =
       collectionUUID: 'test-collection-uuid'
+    @course = new Course(STATUS.courses[0])
+    @sandbox = sinon.sandbox.create()
+    @sandbox.stub(User, 'getCourse').returns(@course)
+    @sandbox.stub(User, 'isLoggedIn').returns(true)

+  afterEach ->
+    @sandbox.restore()

   it 'renders new if course is new', ->
+    sinon.stub(@course, 'isRegistered').returns(false)
     Testing.renderComponent( Registration, props: @props ).then ({element}) ->
       expect(
         ReactTestUtils.scryRenderedComponentsWithType(element, NewCourseRegistration)
@@ -22,9 +30,17 @@

   it 'renders modify if course is already registered', ->
-    course = new Course(STATUS.courses[0])
-    sinon.stub(User, 'getCourse').returns(course)
     Testing.renderComponent( Registration, props: @props ).then ({element}) ->
       expect(
         ReactTestUtils.scryRenderedComponentsWithType(element, ModifyCourseRegistration)
       ).not.to.be.empty
+
+  it 'asks to enroll or login if user doesnt have course and isnt logged in', ->
+    User.getCourse.restore()
+    User.isLoggedIn.restore()
+    @sandbox.stub(User, 'isLoggedIn').returns(false)
+    @sandbox.stub(User, 'getCourse').returns(null)
+    Testing.renderComponent( Registration, props: @props ).then ({element}) ->
+      expect(
+        ReactTestUtils.scryRenderedComponentsWithType(element, EnrollOrLogin)
+      ).not.to.be.empty


Removing debug time till needed.

Removing debug time till needed.

--- src/timber.coffee
+++ src/timber.coffee
@@ -559,8 +559,6 @@
         @timestamp = null
         @continue = true

-        @_debugElapsedTime = null
-
     # Start the engine's event loop.
     start : () ->
         @continue = true
@@ -584,7 +582,7 @@

         # Determine how much time has elapsed since the last loop.
         now = new Date()
-        elapsed = @_debugElapsedTime || now - @timestamp
+        elapsed = now - @timestamp

         # Update the state of the world.
         @collisionHandler.elapse(@elements, elapsed)


Created new IntroductionTooltipController. Finalised v1.

--- client/app/CommonViews/introductiontooltip.coffee
+++ client/app/CommonViews/introductiontooltip.coffee
@@ -4,55 +4,114 @@

     super options, data

-    introData = @getData() or @getIntroData() # make sure we have the data
-    return unless introData # we know introId but we have no JIntroSnippet for this view, should return
-    @setData introData unless @getData() # we just know the intro view and should find the data, called from viewAppended
+    {tooltipView, parentView} = @getOptions()

-    {@introId} = @getData() # we have an intro view and data for this view, go on!
-    @storage   = @getSingleton("mainController").introductionTooltipStatusStorage # to store tooltip status
-    return if @shouldNotDisplay() # user has already closed this introduction before, so don't show it again
-
-    parentView = @getOptions().parentView or @getParentView() # we came from db query and don't know the view, should find
-    return unless parentView # view not appended to dom yet, probably came from db query
-
-    view       = null
-    try view   = eval Encoder.htmlDecode @getData().snippet.split("@").join("this.") # trying to eval snippet
-    return unless view instanceof KDView # we will add this as a subview, should be a KDView instance
-
-    view.addSubView @closeButton = new KDCustomHTMLView
+    tooltipView.addSubView @closeButton = new KDCustomHTMLView
       tagName  : "span"
       cssClass : "close-icon"
       click    : => @close()

-    parentView.setTooltip {
-      view,
-      cssClass: "introduction-tooltip"
-    }
+    parentView.setTooltip
+      view     : tooltipView
+      cssClass : "introduction-tooltip"
+      sticky   : yes
+
     @utils.defer =>
       parentView.tooltip.show()

   close: ->
-    @storage.setValue @introId, yes
+    @emit "IntroductionTooltipClosed"

-  shouldNotDisplay: ->
-    return @storage.getValue(@introId) is yes

-  getIntroData: ->
-    {introSnippets} = @getSingleton "mainController"
+class IntroductionTooltipController extends KDController
+
+  constructor: (options = {}, data) ->
+
+    super options, data
+
+    @shouldAddOverlay = no
+    @visibleTooltips  = []
+
+    @getSingleton("mainController").on "FrameworkIsReady", =>
+      @init()
+
+  init: ->
+    KD.remote.api.JIntroSnippet.fetchAll (err, snippets) =>
+      return log err if err # TODO: error handling
+
+      @introductionTooltipStatusStorage = new AppStorage "IntroductionTooltipStatus"
+      @introductionTooltipStatusStorage.fetchStorage (storage) =>
+        @introSnippets = snippets
+        for snippet in snippets
+          @shouldAddOverlay = yes if snippet.overlay
+          for item in snippet.snippets
+            item.expiryDate = snippet.expiryDate
+            @createInstance null, item
+
+        # @addOverlay() if @shouldAddOverlay and @visibleTooltips.length > 0
+
+  createInstance: (parentView, data) ->
+    assets = @getAssets parentView, data
+    return unless assets
+
+    {parentView, tooltipView} = assets
+    tooltip = new IntroductionTooltip {
+      parentView
+      tooltipView
+    }
+    , assets.data
+
+    @visibleTooltips.push tooltip
+
+    tooltip.on "IntroductionTooltipClosed", =>
+      @close parentView.getOptions().introId
+      parentView.tooltip.destroy()
+      @visibleTooltips.splice @visibleTooltips.indexOf(tooltip), 1
+
+  getAssets: (parentView, data) ->
+    return no if not @introSnippets
+
+    introData = data or @getIntroData parentView # make sure we have the data
+    if not data and introData # we just know the intro view and should find the data, called from viewAppended
+      @setData introData
+      data = introData
+    return no unless data # we know introId but we have no JIntroSnippet for this view, should return
+
+    {introId} = data # we have an intro view and data for this view, go on!
+    @storage  = @introductionTooltipStatusStorage # to store tooltip status
+    return no if @shouldNotDisplay introId # user has already closed this introduction before, so don't show it again
+
+    parentView = parentView or @getParentView data # we came from db query and don't know the view, should find
+    return no unless parentView # view not appended to dom yet, probably came from db query
+
+    tooltipView = null
+    try tooltipView = eval Encoder.htmlDecode data.snippet.split("@").join("this.") # trying to eval snippet
+    return no unless tooltipView instanceof KDView # we will add this as a subview, should be a KDView instance
+
+    return { parentView, tooltipView, data }
+
+  shouldNotDisplay: (introId) ->
+    return @storage.getValue(introId) is yes
+
+  getIntroData: (parentView) ->
+    {introId} = parentView.getOptions()
+    {introSnippets} = @
     ourSnippet = null
     for introSnippet in introSnippets
       for snippet in introSnippet.snippets
-        if @getOptions().parentView?.getOptions().introId is snippet.introId
+        if introId is snippet.introId
           ourSnippet = snippet
-      return ourSnippet
+    return ourSnippet

-  getParentView: ->
-    view      = null
-    {introId} = @getData()
-    instances = KD.introInstances
+  getParentView: (data) ->
+    return KD.introInstances[data.introId] or null

-    for key, instance of instances
-      if instance.getOptions().introId is introId
-        view = instance
+  close: (tooltipIntroId) ->
+    @storage.setValue tooltipIntroId, yes

-    return view+  addOverlay: ->
+    @overlay = $ "<div/>",
+      class : "kdoverlay"
+    @overlay.hide()
+    @overlay.appendTo "body"
+    @overlay.fadeIn 200


disabled the VideoPopup for production deployment

--- client/app/MainApp/maincontroller/mainview.coffee
+++ client/app/MainApp/maincontroller/mainview.coffee
@@ -127,6 +127,8 @@
         else
           @videoButton.setClass "active"
           @popupList.show()
+
+    @videoButton.hide()

     @popupList = new VideoPopupList
       cssClass      : "hidden"


Apps: When I added an empty screenshots array to default manifest file, I have  to check array.length instead of existence of the array.

--- client/app/Applications/Apps.kdapplication/Views/appdetailsview.coffee
+++ client/app/Applications/Apps.kdapplication/Views/appdetailsview.coffee
@@ -42,7 +42,7 @@
       @parent.$('.profilearea').removeClass "cast-shadow"

   pistachio:->
-    if @getData().manifest.screenshots
+    if @getData().manifest.screenshots.length
       screenshots = """
         Screenshots

{{> @slideShow}}


sanitized title & thumbnail_title

sanitized title & thumbnail_title

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -334,6 +334,8 @@
 # TODO: Create a Joi plugin for this https://github.com/hapijs/joi/issues/577
 sanitize = (article) ->
   sanitized = _.extend article,
+    title: sanitizeHtml article.title
+    thumbnail_title: sanitizeHtml article.thumbnail_title
     lead_paragraph: sanitizeHtml article.lead_paragraph
     sections: for section in article.sections
       section.body = sanitizeHtml section.body if section.type is 'text'


Remove WorkspaceElement::handleWindowFocus

We were getting stack traces in specs when the window got focused caused
by this subscription never getting disposed. We investigated, and can’t
find a case where removing this causes any issues.

Tested:
* Loading/reloading empty window
* Loading/reloading window with pane splits
* Opening/closing dev tools
* Switching focus between atom windows
* Switching focus between applications

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/workspace-element.coffee
+++ src/workspace-element.coffee
@@ -67,11 +67,7 @@
   setModel: (@model) ->
     @paneContainer = atom.views.getView(@model.paneContainer)
     @verticalAxis.appendChild(@paneContainer)
-
     @addEventListener 'focus', @handleFocus.bind(this)
-    handleWindowFocus = @handleWindowFocus.bind(this)
-    window.addEventListener 'focus', handleWindowFocus
-    @subscriptions.add(new Disposable -> window.removeEventListener 'focus', handleWindowFocus)

     @panelContainers =
       top: @model.panelContainers.top.getView()
@@ -106,9 +102,6 @@

   handleFocus: (event) ->
     @model.getActivePane().activate()
-
-  handleWindowFocus: (event) ->
-    @handleFocus(event) if document.activeElement is document.body

   focusPaneViewAbove: -> @paneContainer.focusPaneViewAbove()


Fix typo in OpServ

Fix typo in OpServ

--- handlers/opserv.coffee
+++ handlers/opserv.coffee
@@ -65,7 +65,7 @@
 	switch command
 		when "shutdown"
 			db.checkPermissionByMessage message, 'opserv.shutdown', (hasPermission) ->
-				if permission
+				if hasPermission
 					do commands.shutdown
 				else
 					callback?()


REPL blank line fix

REPL blank line fix

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -77,6 +77,7 @@
 # Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
 # The CoffeeScript REPL uses this to run the input.
 exports.eval = (code, options = {}) ->
+  return unless code = code.trim()
   sandbox = options.sandbox
   unless sandbox
     sandbox =
@@ -89,7 +90,7 @@
   sandbox.__dirname  = path.dirname sandbox.__filename
   o = {}; o[k] = v for k, v of options
   o.bare = on # ensure return value
-  js = compile "_=(#{code.trim()}\n)", o
+  js = compile "_=(#{code}\n)", o
   vm.runInNewContext js, sandbox, sandbox.__filename

 # Instantiate a Lexer for our use here.


oauth: remove console.log

oauth: remove console.log

--- client/landing/app/MainApp/oauthcontroller.coffee
+++ client/landing/app/MainApp/oauthcontroller.coffee
@@ -16,7 +16,6 @@
     mainController = KD.getSingleton "mainController"
     if err then notify err
     else
-      console.log "authCompleted", provider
       mainController.emit "ForeignAuthCompleted", provider

   notify = (err)->


config: use mongo-auto:latest for docker name

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -370,7 +370,7 @@
     }

     function runMongoDocker () {
-        docker run -d -p 27017:27017 --name=mongo koding/mongo:2016-10-11
+        docker run -d -p 27017:27017 --name=mongo koding/mongo-auto:latest
         waitMongoReady
     }


more tree-view stuff

more tree-view stuff

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -53,8 +53,8 @@
   's':   'tree-view:open-selected-entry-right'
   'c':   'core:move-up'
   't':   'core:move-down'
-  'h':   'core:move-left'
-  'n':   'core:move-right'
+  'h':   'tree-view:collapse-directory'
+  'n':   'tree-view:open-selected-entry'

 'atom-workspace atom-text-editor':


Add error message

Add error message

--- server/code/model/dataset.coffee
+++ server/code/model/dataset.coffee
@@ -95,6 +95,8 @@
     User.findByShortName @user, (err, user) =>
       if err
         return callback err
+      if not user
+        return callback "cleanCrontab user not found: " + @user
       _exec
         user: user
         boxName: @box


made hadlakat nerot not round down to nearest 5 minutes

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -122,7 +122,7 @@
     when @hebrew_date.is7thDayOfPesach() || @hebrew_date.is1stDayOfShabuot() then @plag()
     when @hebrew_date.is2ndDayOfYomTob() && !@hebrew_date.isErebShabbat() then minutes_before_event(@zmanim.set_hakochabim, 10)
     else @sunset
-  hadlakat_nerot: -> if @has_hadlakat_nerot_after_set_hakochabim() then @zmanim.set_hakochabim else minutes_before_event(@sunset, 19)
+  hadlakat_nerot: -> if @has_hadlakat_nerot_after_set_hakochabim() then @zmanim.set_hakochabim else moment(@sunset).subtract(19, 'minutes')
   has_hadlakat_nerot_after_set_hakochabim: -> (@hebrew_date.isErebYomTob() && @hebrew_date.isShabbat()) || (@hebrew_date.is1stDayOfYomTob() && !@hebrew_date.isErebShabbat())
   has_hadlakat_nerot_before_sunset: -> @hebrew_date.isErebShabbat() || @hebrew_date.isErebYomKippur() || @hebrew_date.isErebYomTob()
   hadlakat_nerot_text: ->


Do the count properly

Do the count properly

--- models/organization.coffee
+++ models/organization.coffee
@@ -71,7 +71,7 @@

   if callback
     promise.addBack callback
-  query = @model('Badge').count('issuer_id', @id)
+  query = @model('Badge').count({'issuer_id': @id})
   query.exec (err, result)->
     promise.resolve(err, result)


Adds a method for street addresses

Adds a method for street addresses

--- models/partner_show.coffee
+++ models/partner_show.coffee
@@ -201,6 +201,9 @@
   formatCity: =>
     @get('location')?.city?.trim()

+  formatStreetAddress: ->
+    @get('location')?.address?.trim()
+
   formatFeedItemHeading: ->
     return @get('name') if @get('name')?.length > 0
     @formatArtists @maxDisplayedArtists


hide the deploy panel by default

hide the deploy panel by default

--- lib/heroku-deploy-view.coffee
+++ lib/heroku-deploy-view.coffee
@@ -14,6 +14,7 @@
       super
       @addClass('overlay from-top')
       @modalPanel ?= atom.workspace.addModalPanel(item: this)
+      @modalPanel.hide()
       @loadApps

     loadApps: ->


Fix avatars in the network graph for relative-url setups.

--- app/assets/javascripts/branch-graph.js.coffee
+++ app/assets/javascripts/branch-graph.js.coffee
@@ -201,7 +201,7 @@
       stroke: @colors[commit.space]
       "stroke-width": 2
     )
-    r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20)
+    r.image(gon.relative_url_root + commit.author.icon, avatar_box_x, avatar_box_y, 20, 20)
     r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
       "text-anchor": "start"
       font: "14px Monaco, monospace"
@@ -274,7 +274,7 @@
 Raphael::commitTooltip = (x, y, commit) ->
   boxWidth = 300
   boxHeight = 200
-  icon = @image(commit.author.icon, x, y, 20, 20)
+  icon = @image(gon.relative_url_root + commit.author.icon, x, y, 20, 20)
   nameText = @text(x + 25, y + 10, commit.author.name)
   idText = @text(x, y + 35, commit.id)
   messageText = @text(x, y + 50, commit.message)


More updates to user name in the connect 4.

--- app/assets/javascripts/connect_four.js.coffee
+++ app/assets/javascripts/connect_four.js.coffee
@@ -92,7 +92,6 @@

 #Private
 recursive_add_to_col = (column,row,color,callback) ->
-
   return connect_four.invalid_msg("Invalid move [#{color}] tried to move to column [#{column+1}]") if row < 0 or column < 0 or column > $("canvas#board").width()/100-1
   return connect_four.add column,row,color,callback if $("#pos#{column}_#{row}").length==0
   recursive_add_to_col column,row-1,color,callback


fix issue with renaming asset title (new chr);

--- app/assets/javascripts/loft/asset-item.coffee
+++ app/assets/javascripts/loft/asset-item.coffee
@@ -11,7 +11,7 @@
 # -----------------------------------------------------------------------------
 class @LoftAssetItem extends Item
   constructor: (@module, @path, @object, @config) ->
-    @$el =$ "

"
+    @$el =$ "

"
     @render()

@@ -74,7 +74,7 @@

     # input for assets name
-    name = @$el.attr('data-title')
+    name = @$title.text()
     @$name      =$ "

"
     @$nameInput =$ "<input type='text' value='#{ name }' />"
     @$name.append @$nameInput


Check status instead of error message

Check status instead of error message

--- app/assets/javascripts/login_prompt.js.coffee
+++ app/assets/javascripts/login_prompt.js.coffee
@@ -1,5 +1,5 @@
 $(document).on 'ajax:error', (event, xhr, status, error) ->
-  if error == 'Unauthorized'
+  if xhr.status == 401
     $.fancybox.open
       href: '#js-login-prompt'
       scrolling: 'visible' # for button outlines and shadows