Update course names

Update course names

--- app/views/courses/mock1/CoursesMockData.coffee
+++ app/views/courses/mock1/CoursesMockData.coffee
@@ -40,7 +40,7 @@
     image: '/images/pages/courses/102_info.png'
   },
   {
-    title: 'CS Course 3'
+    title: 'Computer Science 3'
     description: 'Learn how to handle input.'
     topics: ['If Statements', 'Arithmetic', 'Input Handling']
     duration: 5
@@ -48,7 +48,7 @@
     image: '/images/pages/courses/103_info.png'
   },
   {
-    title: 'CS 4'
+    title: 'Computer Science 4'
     description: 'Time to tackle arrays and some pvp stuff.'
     topics: ['Loops', 'Break Statements', 'Arrays']
     duration: 5
@@ -56,7 +56,7 @@
     image: '/images/pages/courses/104_info.png'
   },
   {
-    title: 'Course 5'
+    title: 'Computer Science 5'
     description: 'Time to tackle arrays and some PVP.'
     topics: ['Break Statements', 'Arrays', 'Object Literals']
     duration: 5
@@ -64,7 +64,7 @@
     image: '/images/pages/courses/105_info.png'
   },
   {
-    title: 'Course 6'
+    title: 'Computer Science 6'
     description: 'For loops!'
     topics: ['Break Statements', 'Object Literals', 'For loops']
     duration: 5
@@ -72,7 +72,7 @@
     image: '/images/pages/courses/106_info.png'
   },
   {
-    title: 'Course 7'
+    title: 'Computer Science 7'
     description: 'Functions!'
     topics: ['Object Literals', 'For loops', 'Functions']
     duration: 5
@@ -80,7 +80,7 @@
     image: '/images/pages/courses/107_info.png'
   },
   {
-    title: 'CS 108'
+    title: 'Computer Science 8'
     description: 'Maths.'
     topics: ['For loops', 'Functions', 'Math Operations']
     duration: 5
@@ -88,7 +88,7 @@
     image: '/images/pages/courses/107_info.png'
   },
   {
-    title: 'Computer Science 109'
+    title: 'Computer Science 9'
     description: 'Vectors and strings.'
     topics: ['Vectors', 'Advanced Strings']
     duration: 5


Pass encoding as string to fs.readFileSync() to maintain backwards compatibility

--- test/gyp.coffee
+++ test/gyp.coffee
@@ -48,7 +48,7 @@
 			execGruntTask 'configure', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync './build/config.gypi', {encoding: 'utf8'}
+				configGypi = fs.readFileSync './build/config.gypi', 'utf8'
 				if configGypi.indexOf('"default_configuration": "Release"') < 0
 					return done(new Error 'expected config.gypi to be configured for release build')

@@ -60,7 +60,7 @@
 			execGruntTask 'configureDebug', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync './build/config.gypi', {encoding: 'utf8'}
+				configGypi = fs.readFileSync './build/config.gypi', 'utf8'
 				if configGypi.indexOf('"default_configuration": "Debug"') < 0
 					return done(new Error 'expected config.gypi to be configured for debug build')


Fixing merging order (RequireJS first of all), and adding an initializer for AMD use.

--- src/toaster/filetype/coffee/optimizer.coffee
+++ src/toaster/filetype/coffee/optimizer.coffee
@@ -125,15 +125,22 @@

   merge_everything:->
     console.log 'Merging files..'.grey
-    buffer = "//---------------------------------------- vendors\n\n\n"
-    buffer += @vendors.merge_to_str() + '\n\n'
+
+    buffer = ""

     if @config.browser.amd?
       buffer += "//---------------------------------------- amd loader\n\n\n"
       buffer += @loader.get_amd_loader()

+    buffer += "//---------------------------------------- vendors\n\n\n"
+    buffer += @vendors.merge_to_str() + '\n\n'
+
     buffer += "//---------------------------------------- files\n\n\n"
     buffer += @merge_files() + '\n'
+
+    if @config.browser.amd?
+      buffer += "//---------------------------------------- amd initializer\n\n\n"
+      buffer += "require( ['#{@config.browser.amd.main}'] );"

     if @config.browser?.optimize?.minify
       console.log 'Minifying..'.grey


Fixing method indentation (1 less).

Fixing method indentation (1 less).

--- src/toaster/filetype/coffee/optimizer.coffee
+++ src/toaster/filetype/coffee/optimizer.coffee
@@ -34,85 +34,85 @@
       if @config.browser.amd
         @loader.write_loader()

-  optimize:()->
-
-      unless @config.browser?.optimize?
-        return
-
-      # clear release folder
-      @tree.clear_release_dir()
-
-      # if merge is set, optimization will just merge everything
-      if @config.browser.optimize.merge?
-        return @merge_everything()
-
-      log 'Optimizing project...'
-
-      paths = {}
-      layers = []
-      included = []
-      ordered = @reorder (@tree.files.slice 0)
-
-      for layer_name, layer_deps of @config.browser.optimize.layers
-
-        layers.push layer_name
-
-        contents = ''
-        for dep in layer_deps
-
-          # gets dependency chain
-          found = (ArrayUtil.find ordered, 'filepath': "#{dep}.coffee")
-
-          # if nothing is found
-          unless found?
-            # checks if it was already included
-            is_included = (ArrayUtil.find included, 'filepath': "#{dep}.coffee")
-
-            # and if not..
-            unless is_included?
-              msg = "Cannot find module `#{dep}` for layer `#{layer_name}`."
-              msg += '\nCheck your `toaster.coffee` config file.'
-              error msg
-
-            continue
-
-          # scripts pack (all dependencies resolved)
-          pack = ordered.splice 0, found.index + 1
-
-          # adding all to included array
-          included = included.concat pack
-
-          # increments the layer contents and map the script location into paths
-          for script in pack
-            paths[script.filepath.replace '.coffee', ''] = layer_name
-            contents += script.compile_to_str @config
-
-        # if there's something to be written
-        if contents isnt ''
-
-          if @config.browser?.optimize?.minify
-            contents = MinifyUtil.min contents
-
-          # write layer
-          layer_path = path.join @config.release_dir, "#{layer_name}.js"
-          fs.writeFileSync layer_path, contents
-
-          # notify user through cli
-          relative_path = layer_path.replace @toaster.basepath, ''
-          relative_path = relative_path.substr 1 if relative_path[0] is path.sep
-          msg = "#{'✓ Layer optimized: '.bold}#{layer_name} -> #{relative_path}"
-          log msg.green
-
-        # otherwise if it's empty just inform user through the cli
-        else
-          msg = "#{'✓ Layer is empty: '.bold} #{layer_name} -> [skipped]"
-          log msg.yellow
-
-      # write toaster loader and initializer
-      @loader.write_loader paths
-
-      # copy all vendors as well
-      @vendors.copy_to_release true, null, false
+  optimize:->
+
+    unless @config.browser?.optimize?
+      return
+
+    # clear release folder
+    @tree.clear_release_dir()
+
+    # if merge is set, optimization will just merge everything
+    if @config.browser.optimize.merge?
+      return @merge_everything()
+
+    log 'Optimizing project...'
+
+    paths = {}
+    layers = []
+    included = []
+    ordered = @reorder (@tree.files.slice 0)
+
+    for layer_name, layer_deps of @config.browser.optimize.layers
+
+      layers.push layer_name
+
+      contents = ''
+      for dep in layer_deps
+
+        # gets dependency chain
+        found = (ArrayUtil.find ordered, 'filepath': "#{dep}.coffee")
+
+        # if nothing is found
+        unless found?
+          # checks if it was already included
+          is_included = (ArrayUtil.find included, 'filepath': "#{dep}.coffee")
+
+          # and if not..
+          unless is_included?
+            msg = "Cannot find module `#{dep}` for layer `#{layer_name}`."
+            msg += '\nCheck your `toaster.coffee` config file.'
+            error msg
+
+          continue
+
+        # scripts pack (all dependencies resolved)
+        pack = ordered.splice 0, found.index + 1
+
+        # adding all to included array
+        included = included.concat pack
+
+        # increments the layer contents and map the script location into paths
+        for script in pack
+          paths[script.filepath.replace '.coffee', ''] = layer_name
+          contents += script.compile_to_str @config
+
+      # if there's something to be written
+      if contents isnt ''
+
+        if @config.browser?.optimize?.minify
+          contents = MinifyUtil.min contents
+
+        # write layer
+        layer_path = path.join @config.release_dir, "#{layer_name}.js"
+        fs.writeFileSync layer_path, contents
+
+        # notify user through cli
+        relative_path = layer_path.replace @toaster.basepath, ''
+        relative_path = relative_path.substr 1 if relative_path[0] is path.sep
+        msg = "#{'✓ Layer optimized: '.bold}#{layer_name} -> #{relative_path}"
+        log msg.green
+
+      # otherwise if it's empty just inform user through the cli
+      else
+        msg = "#{'✓ Layer is empty: '.bold} #{layer_name} -> [skipped]"
+        log msg.yellow
+
+    # write toaster loader and initializer
+    @loader.write_loader paths
+
+    # copy all vendors as well
+    @vendors.copy_to_release true, null, false

   merge_files:->


testsuite.validate(): Fix missing schema

Also make passed: false when schemas are missing,
as this should never happen and definitely does
not mean that the thing validated!

--- src/testsuite.coffee
+++ src/testsuite.coffee
@@ -13,7 +13,8 @@
   for name, schema of schemas
     tv4.addSchema schema.id, schema

-  results = tv4.validateMultiple obj, 'testsfile'
+  results = tv4.validateMultiple obj, 'testsfile.json'
+  results.passed = false if results.missing.length
   return results

 normalize = (suite) ->


Tweak message when prompting for a directory

--- src/tree-view.coffee
+++ src/tree-view.coffee
@@ -208,9 +208,13 @@
     entry = @selectedEntry()
     return unless entry
     oldPath = entry.getPath()
+    if fs.isFile(oldPath)
+      prompt = "Enter the new path for the file."
+    else
+      prompt = "Enter the new path for the directory."

     dialog = new Dialog
-      prompt: "Enter the new path for the file."
+      prompt: prompt
       path: @rootView.project.relativize(oldPath)
       select: true
       iconClass: 'move'


removed unneeded print out

removed unneeded print out

--- src/uninstall.coffee
+++ src/uninstall.coffee
@@ -70,7 +70,6 @@
             packageVersion = @getPackageVersion(packageDirectory)
             fs.removeSync(packageDirectory)
             if packageVersion
-              process.stdout.write "packVersion"
               uninstallsToRegister.push({packageName, packageVersion})
           else
             throw new Error("Does not exist")


Revert earlier change that makes removeFromParentNode only fire if the node was in parentNode. It's not needed.

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -108,15 +108,14 @@

   removeFromParentNode: (destroy) ->
     node = @get('node')
-    isInDOM = document.body.contains(node) and @node.parentNode == @parentNode
+    isInDOM = document.body.contains(node)

     @propagateToSubviews('viewWillDisappear') if isInDOM

-    if isInDOM
-      if destroy
-        Batman.DOM.destroyNode(@node)
-      else
-        @node.parentNode.removeChild(@node)
+    if destroy
+      Batman.DOM.destroyNode(@node)
+    else
+      @node.parentNode.removeChild(@node)

     @propagateToSubviews('isInDOM', false)
     @propagateToSubviews('viewDidDisappear') if isInDOM


pend for now (  ._.)

pend for now (  ._.)

--- apps/home/components/top_rail/test/view.coffee
+++ apps/home/components/top_rail/test/view.coffee
@@ -8,7 +8,7 @@
 HomeTopRailView = benv.requireWithJadeify resolve(__dirname, '../view'), ['template']
 Q = require 'bluebird-q'

-describe 'HomeTopRailView', ->
+xdescribe 'HomeTopRailView', ->
   before (done) ->
     benv.setup =>
       benv.expose $: benv.require 'jquery'


Fixes #3391 - Unable to open object manager select field modal if options contains invalid data (like undefined or null).

--- app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee
+++ app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee
@@ -8,7 +8,12 @@
       params.data_option = params.data_option_new

     if attribute.value == 'select' && params.data_option? && params.data_option.options?
-      sorted = _.map params.data_option.options, (value, key) -> [key.toString(), value.toString()]
+      sorted = _.map(
+        params.data_option.options, (value, key) ->
+          key = '' if !key || !key.toString
+          value = '' if !value || !value.toString
+          [key.toString(), value.toString()]
+      )
       params.data_option.sorted = sorted.sort( (a, b) -> a[1].localeCompare(b[1]) )

     item = $(App.view('object_manager/attribute')(attribute: attribute))


change to update mode when filtering through the new validation error link

--- app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
@@ -183,6 +183,7 @@
           @set(selectedValuesName, errorValue)
         else
           @set(selectedValuesName, [errorValue])
+      @set('filtersVisible', false)
       @endPropertyChanges()

     setVisibleShipments: (shipments) ->


Don't send Authorization header if we don't have token

--- assets/scripts/app/adapters/application.coffee
+++ assets/scripts/app/adapters/application.coffee
@@ -10,8 +10,8 @@

     hash.headers['accept'] = 'application/json; version=2'

-    token = Travis.sessionStorage.getItem('travis.token')
-    hash.headers['Authorization'] ||= "token #{token}"
+    if token = Travis.sessionStorage.getItem('travis.token')
+      hash.headers['Authorization'] ||= "token #{token}"

     hash


cleanup

cleanup

--- src/scripts/brain_btn_panel.cjsx
+++ src/scripts/brain_btn_panel.cjsx
@@ -78,22 +78,11 @@
   render: ->
     btnStyle =
       backgroundColor : @props.player.color
-      cursor          : 'pointer'
-      borderRadius    : '40px'
-      border          : "2px solid black"
-      margin          : "10px"
-      width           : "80px"
-      height          : "80px"
-      boxShadow       : "2px 2px 10px rgba(0,0,0,0.35)"
-      display         : 'inline-block'
-      float           : 'right'

-    if @state.selected
-      _.extend btnStyle,
-        border    : "2px solid green"
-        boxShadow : "0 0 10px green"
+    className = "brain-btn loco"
+    className += " selected" if @state.selected

-

+


fixing nextQuote

fixing nextQuote

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -108,7 +108,6 @@

   $('.right-arrow').click ->
     if img_number == 4
-      console.log 'hello'
       left_gal_img = 'gal2.jpg'
       right_gal_img = 'gal1.jpg'
       img_number = 0
@@ -144,25 +143,25 @@
     'The online art classes provided by Doodles Academy are invaluable to me as a homeschool mom and teacher... I also appreciate the flexibility of the program. We can adjust it to our life, schedule, and health.'
     'I absolutely love your website and have begun to use it in my classroom quite frequently ... some of my students love the website so much that they have done some lessons on their own at home.'
   ]
+
   quotePersons = [
     'Kara Bloom'
     'Laura Harris'
     'Erin Scott'
     'Julie Rains'
   ]
+
   quoteNumber = 0
-  lastQuote = quotes.length
   $('#quotes p').append quotes[quoteNumber]
   $('.quoted-parent').append '<p>' + quotePersons[quoteNumber] + '</p>'
-  $('#dot' + quoteNumber + ' img').attr("src", "assets/bluedot.png");
+  $('#dot' + quoteNumber + ' img').attr("src", "assets/bluedot.png")

   nextQuote = ->
-    $("#dot#{lastQuote} img").attr 'src', 'assets/dot.png'
+    $(".dot img").attr 'src', 'assets/dot.png'
     $('#quotes p').text quotes[quoteNumber]
     $('.quoted-parent p').text quotePersons[quoteNumber]
     $("#dot#{quoteNumber} img").attr 'src', 'assets/bluedot.png'
     if quoteNumber < quotes.length - 1 then (quoteNumber += 1) else (quoteNumber = 0)
-    if lastQuote < quotes.length - 1 then (lastQuote += 1) else (lastQuote = 0)
     setTimeout nextQuote, 8000
     return


 Merge branch 'master.mixpanel' into 'master'

Master.Mixpanel

--- client/landing/Intro/home.coffee
+++ client/landing/Intro/home.coffee
@@ -116,18 +116,6 @@
       offset    :
         top     : 25
         left    : 25
-
-    new MixpanelScrollTracker
-      attribute: 'section',
-      event: '/ scroll to',
-      markers: [
-        { position: 362,  value: 'Teamwork screenshot'    }
-        { position: 627,  value: 'Feature explanation'    }
-        { position: 1495, value: 'Activity screenshot'    }
-        { position: 1995, value: 'Enterprise explanation' }
-        { position: 2270, value: 'Enterprise contact'     }
-        { position: 2864, value: 'Scrolled to bottom'     }
-      ]

   pistachio:->


Add in missing error check

Add in missing error check

--- app/coffee/OutputFileFinder.coffee
+++ app/coffee/OutputFileFinder.coffee
@@ -9,7 +9,8 @@
 		for resource in resources
 			incomingResources[resource.path] = true

-		OutputFileFinder._getAllFiles directory, (error, allFiles) ->
+		OutputFileFinder._getAllFiles directory, (error, allFiles = []) ->
+			return callback(error) if error?
 			jobs = []
 			outputFiles = []
 			for file in allFiles


handle both end and error

handle both end and error

--- lib/connection/local.coffee
+++ lib/connection/local.coffee
@@ -51,6 +51,7 @@
     proc.message = (m) -> sock.write JSON.stringify m
     client.readStream sock
     sock.on 'end', -> client.detach()
+    sock.on 'error', -> client.detach()
     proc.ready = -> true
     client.flush()
     proc


unnecessary routes removed from server

unnecessary routes removed from server

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -42,7 +42,7 @@
       pattern: 'web'
     worker_social:
       pattern: 'social'
-
+
   incService = (serviceKey, inc) ->
     for key, value of services
       if serviceKey.indexOf(value.pattern) > -1
@@ -103,19 +103,13 @@
     JActivityCache.latest (err, cache)->
       if err then console.warn err
       console.log "latest: #{Date.now() - startTime} msecs!"
-      res.send if cache then cache.data else []
+      return res.send if cache then cache.data else {}

   app.get "/-/cache/before/:timestamp", (req, res)->
     {JActivityCache} = koding.models
     JActivityCache.before req.params.timestamp, (err, cache)->
       if err then console.warn err
-      res.send if cache then cache.data else []
-
-  app.get "/-/cache/id/:id", (req, res)->
-    {JActivityCache} = koding.models
-    JActivityCache.byId req.params.id, (err, cache)->
-      if err then console.warn err
-      res.send if cache then cache.data else []
+      res.send if cache then cache.data else {}

   app.get "/Logout", (req, res)->
     res.clearCookie 'clientId'


moved styling to css

moved styling to css

--- plugins/image.coffee
+++ plugins/image.coffee
@@ -370,9 +370,6 @@
                     console.log(event.currentTarget)
                     $('<div>').css({
                         backgroundImage: "url(" + $(event.currentTarget).attr('src') + ")"
-                        width: '100px'
-                        height: '100px'
-                        zIndex: '1005'
                     }).addClass('customHelper').appendTo('body');


Fixing all done calls

Fixing all done calls

--- tests/www/tests/functional/title.coffee
+++ tests/www/tests/functional/title.coffee
@@ -32,7 +32,7 @@
             browser.elementByClassName 'menu', (err, el)->
               should.not.exist err
               should.exist el
-              do done
+              pass done

       # /title/theoricus
       # ------------------------------------------------------------------------
@@ -43,20 +43,20 @@
             should.not.exist err
             el.click (err)->
               should.not.exist err
-              do done
+              pass done

         it 'wait until page is rendered', (done)->
           browser.waitForCondition conds.is_rendered, timeout, 30, (err, res)->
             should.not.exist err
             res.should.be.true
-            do done
+            pass done

         it 'check if title is `Theoricus`', (done)->
           browser.title (err, title)->
             should.not.exist err
             should.exist title
             title.should.equal 'Theoricus'
-            do done
+            pass done

       # /title/is
       # ------------------------------------------------------------------------
@@ -66,20 +66,20 @@
             should.not.exist err
             el.click (err)->
               should.not.exist err
-              do done
+              pass done

         it 'wait until page is rendered', (done)->
           browser.waitForCondition conds.is_rendered, timeout, 30, (err, res)->
             should.not.exist err
             res.should.be.true
-            do done
+            pass done

         it 'check if title is `Theoricus`', (done)->
           browser.title (err, title)->
             should.not.exist err
             should.exist title
             title.should.equal 'Theoricus is'
-            do done
+            pass done

       # /title/awesome
       # ------------------------------------------------------------------------
@@ -88,17 +88,17 @@
           browser.elementById 'title-awesome', (err, el)->
             el.click (err)->
               should.not.exist err
-              do done
+              pass done

         it 'wait until page is rendered', (done)->
           browser.waitForCondition conds.is_rendered, timeout, 30, (err, res)->
             should.not.exist err
             res.should.be.true
-            do done
+            pass done

         it 'check if title is `Theoricus`', (done)->
           browser.title (err, title)->
             should.not.exist err
             should.exist title
             title.should.equal 'Theoricus is awesome'
-            do done+            pass done


same again, but for scraperwiki.js

same again, but for scraperwiki.js

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -7,7 +7,7 @@
 jQuery is required.
 ###

-console = console or
+window.console = window.console or
   log: ->

 scraperwiki = sw =


Add option to skip cache version in links

--- src/turbolinks/controller.coffee
+++ src/turbolinks/controller.coffee
@@ -87,10 +87,14 @@
   getCachedSnapshotForLocation: (location) ->
     @cache.get(location)

+  shouldCacheSnapshot: ->
+    @getSetting("cache-control") isnt "no-cache"
+
   cacheSnapshot: ->
-    @notifyApplicationBeforeCachingSnapshot()
-    snapshot = @view.getSnapshot()
-    @cache.put(@lastRenderedLocation, snapshot)
+    if @shouldCacheSnapshot()
+      @notifyApplicationBeforeCachingSnapshot()
+      snapshot = @view.getSnapshot()
+      @cache.put(@lastRenderedLocation, snapshot)

   # Scrolling


made language bar work in search <input>

--- client/ide.coffee
+++ client/ide.coffee
@@ -158,7 +158,14 @@
   ttid = null # tooltip timeout id
   $ '.lbar'
     .on 'mousedown', -> false
-    .on 'mousedown', 'b', (e) -> (for _, widget of wins when widget.hasFocus() then widget.insert $(e.target).text()); false
+    .on 'mousedown', 'b', (e) ->
+      ch = $(e.target).text()
+      # Try to find a focused widget (session/editor) and therefore a focused CodeMirror instance
+      for _, widget of wins when widget.hasFocus() then widget.insert ch; return false
+      # Find the focused element, if present.  It's likely the search <input>
+      if (i = $(':focus')[0]) && (x = i.selectionStart)? && (y = i.selectionEnd)? # TODO: we will also need special code for IE
+        i.value = i.value[...x] + ch + i.value[y..]; i.focus(); i.selectionStart = i.selectionEnd = x + ch.length
+      false
     .on 'mouseout', 'b', -> clearTimeout ttid; ttid = null; $tip.add($tipTriangle).hide(); return
     .on 'mouseover', 'b', (e) ->
       clearTimeout ttid; $t = $ e.target; p = $t.position(); x = $t.text()


re-compute gutter widths on tab activate

--- client/ide.coffee
+++ client/ide.coffee
@@ -41,7 +41,9 @@
     @focusedWin = @wins[0]

     # Tab management
-    @tabOpts = activate: (_, ui) => (widget = @wins[+ui.newTab.attr('id').replace /\D+/, '']).updateSize(); widget.focus(); return
+    @tabOpts = activate: (_, ui) =>
+      widget = @wins[+ui.newTab.attr('id').replace /\D+/, '']
+      widget.updateSize(); widget.focus(); widget.updateGutters(); return
     @$tabs = $('.ui-layout-east, .ui-layout-south').tabs @tabOpts
     @refreshTabs = =>
       for d in ['east', 'south'] when !$(".ui-layout-#{d} li", @$ide).length then @layout.close d


Handle change in min, max and isLemma in CWB multiwords

--- app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_multiword_term.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_multiword_term.jsx.coffee
@@ -10,12 +10,19 @@
     handleTermChanged: React.PropTypes.func.isRequired

-  handleTextChanged: (e) ->
+  changeTerm: (attribute, event) ->
     changedTerm = {}
     changedTerm[k] = @props.term[k] for k of @props.term  # clone the old term
-    changedTerm.word = e.target.value                     # set its text value
+    changedTerm[attribute] = event.target.value
     @props.handleTermChanged(changedTerm, @props.termIndex)

+  handleTextChanged: (e) -> @changeTerm('word', e)
+
+  handleMinChanged: (e) -> @changeTerm('min', e)
+
+  handleMaxChanged: (e) -> @changeTerm('max', e)
+
+  handleIsLemmaChanged: (e) -> @changeTerm('isLemma', e)

   render: ->
     {term, queryHasSingleTerm, isFirst, isLast} = @props
@@ -30,8 +37,8 @@
                 {isFirst ? null :

Interval

-                  <input type="text" className="interval" value={term.min} /> min

-                  <input type="text" className="interval" value={term.max} /> max
+                  <input type="text" className="interval" value={term.min} onChange={this.handleMinChanged} /> min

+                  <input type="text" className="interval" value={term.max} onChange={this.handleMaxChanged} /> max

                 }

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

-                    <input type="checkbox" checked={term.isLemma} /> Lemma
+                    <input type="checkbox" checked={term.isLemma} onChange={this.handleIsLemmaChanged} /> Lemma


added methods to add notes to an encounter


respect border_left, etc

respect border_left, etc

--- static/coffee/common/plot.coffee
+++ static/coffee/common/plot.coffee
@@ -63,10 +63,10 @@
       y_offset:      options.y_offset      ? @mget('y_offset')
       outer_width:   options.outer_width   ? @mget('outer_width')
       outer_height:  options.outer_height  ? @mget('outer_height')
-      border_top:    options.border_top    ? @mget('border_top')    ? @mget('border')
-      border_bottom: options.border_bottom ? @mget('border_bottom') ? @mget('border')
-      border_left:   options.border_left   ? @mget('border_left')   ? @mget('border')
-      border_right:  options.border_right  ? @mget('border_right')  ? @mget('border')
+      border_top:    (options.border_top    ? @mget('border_top'))    ? @mget('border')
+      border_bottom: (options.border_bottom ? @mget('border_bottom')) ? @mget('border')
+      border_left:   (options.border_left   ? @mget('border_left'))   ? @mget('border')
+      border_right:  (options.border_right  ? @mget('border_right'))  ? @mget('border')
     })

     @x_range = options.x_range ? @mget('x_range')


automatically decode the search params for use in location state

--- @client/browser_location.coffee
+++ @client/browser_location.coffee
@@ -31,9 +31,9 @@
   # if the url has query parameters, parse and merge them into params

   for k,v of url_parts.searchObject when k.length > 0
-    loc.query_params[k] = v
-
-  loc.query_params = url_parts.searchObject
+    loc.query_params[decodeURIComponent(k)] = decodeURIComponent(v)
+
+  # loc.query_params = url_parts.searchObject
   delete loc.query_params.u if loc.query_params.u
   delete loc.query_params.t if loc.query_params.t


use interactionobserver instead of getboundingclientrect for shadowing wrapper

--- @client/google_translate.coffee
+++ @client/google_translate.coffee
@@ -40,7 +40,6 @@
   insertTranslationWidget: ->
     subdomain = fetch '/subdomain'

-
     new google.translate.TranslateElement {
         pageLanguage: subdomain.lang
         layout: google.translate.TranslateElement.InlineLayout.SIMPLE
@@ -55,19 +54,33 @@

     return if !wrapper || !translate_el

-    coords = getCoords(wrapper)
+    set_coords = (coords) =>
+      left = coords.left + coords.width / 2 - @refs.translation_el.clientWidth / 2
+      top = coords.top

-    left = coords.left + coords.width / 2 - @refs.translation_el.clientWidth / 2
-    top = coords.top
+      # set by Google Translate when a language is selected, which screws up the top calculation
+      if document.body.style.top == "40px"
+        top -= 40

-    # set by Google Translate when a language is selected, which screws up the top calculation
-    if document.body.style.top == "40px"
-      top -= 40
+      if @local.left != left || top != @local.top
+        @local.left = left
+        @local.top = top
+        save @local

-    if @local.left != left || top != @local.top
-      @local.left = left
-      @local.top = top
-      save @local
+    observer = new IntersectionObserver (entries) =>
+      for entry in entries
+        coords = entry.boundingClientRect
+        coords.left += window.pageXOffset
+        coords.top += window.pageYOffset
+        set_coords
+          left: coords.left + window.pageXOffset
+          top: coords.top + window.pageYOffset
+          width: coords.width
+      observer.disconnect()
+    observer.observe(wrapper)
+
+
+

   componentDidMount: ->
@@ -75,6 +88,7 @@
     @int = setInterval =>
       if google?.translate?.TranslateElement?
         @insertTranslationWidget()
+        @setPosition()
         clearInterval @int
     , 20

@@ -84,8 +98,6 @@
       requestAnimationFrame @setPosition
     , 500

-    @setPosition()
-
   componentWillUnmount: ->
     clearInterval @int
     clearInterval @placer_int


only update x,y of sticky when necessary

Former-commit-id: 906aee482847712cfe9889355267f13eca4d8e0a [formerly 8c9258803125194577f860441ddb25a32a91c617] [formerly ca2f0bff89d143e54d31c2b691e17303a34c1cad [formerly c0c782b47bfe1830625a9a40bce0f2368641a102]] [formerly fffa3be40571582307e131597c57e9f9aa001914 [formerly 67802de1037994c50d90db421d87798806fe411c] [formerly be2bb941164f01be4cf2a80cdaab20f423af7f5f [formerly be2bb941164f01be4cf2a80cdaab20f423af7f5f [formerly 0a29800fe56d8aab0c878d76285fbdc357f8db57]]]]
Former-commit-id: a372194d7dfdcdf6b1dce4de19ff96642c06b43f [formerly bdb8e59975e4c3c281dde9af0d8f0397bfeca30a] [formerly aff377dbc4621ce8795b5946da741f574ccdd46d [formerly 84b45b64db8b2ecdd5c966ded2ab87154261500d]]
Former-commit-id: 14971e62de704467621111145248f658f5d51ee5 [formerly ecbb68d2c14b467de8b202ced2c1a899b7bf2b2f]
Former-commit-id: 83bd0d40871e1babb077e5ca5742aaad3cd3c255
Former-commit-id: c72aac79b17767ae60ff921ed3863b420ceba406

--- @client/sticky_component.coffee
+++ @client/sticky_component.coffee
@@ -67,25 +67,8 @@

     sticky = fetch @key
     if sticky.stuck
-
-      [x, y] = [0, sticky.y]
-
-      if browser.is_mobile
-        positioning_method = 'absolute'
-        # When absolutely positioning, the reference is with respect to the closest
-        # parent that has been positioned. Because sticky.y is with respect to the
-        # document, we need to adjust for the parent offset.
-        y -= $(@getDOMNode()).offsetParent().offset().top
-      else
-        positioning_method = 'fixed'
-
-        # Fixed positioning is relative to the viewport, not the document
-        y -= scroller.viewport_top
-
-        # Adjust for horizontal scroll for fixed position elements because they don't
-        # move with the rest of the content (they're fixed to the viewport).
-        # ScrollLeft is used to offset the fixed element to simulate sticking to the window.
-        x -= $(window).scrollLeft()
+      [x, y] = [sticky.x, sticky.y]
+      positioning_method = if browser.is_mobile then 'absolute' else 'fixed'

       style =
         top: 0
@@ -178,6 +161,7 @@
   viewport_top : document.documentElement.scrollTop || document.body.scrollTop
   last_viewport_top : document.documentElement.scrollTop || document.body.scrollTop
           # caches scroll positions at t and t-1 for use in calculations
+  last_positions : {}

   #######
   # register & unregister
@@ -222,18 +206,53 @@

     if stuck.length > 0
       # Calculate y-positions for all stuck components
-      y_pos = scroller.solveForY stickies
+      y_pos = scroller.solveForY stuck, stickies

       for k in stuck
         sticky = fetch k
-        sticky.y = y_pos[k].value
-
-        if !sticky.stuck
-          scroller.toggleStuck k, stickies[k]
-
-        save sticky
+        scroller.last_positions[k] = y_pos[k].value
+        [x, y] = scroller.adjustForDevice y_pos[k].value, stickies[k]
+        if sticky.y != y || sticky.x != x
+          sticky.y = y
+          sticky.x = x
+          if !sticky.stuck
+            scroller.toggleStuck k, stickies[k]
+
+          save sticky
+
+
+

     scroller.last_viewport_top = scroller.viewport_top
+
+
+  ####
+  # adjustForDevice
+  #
+  # Calculates x & y sticky component values based on the positioning
+  # method used for the particular device.
+  #
+  # On desktop we can safely use the more efficient fixed positioning.
+  # But for mobile, we use absolute positioning because mobile devices
+  # have terrible support for fixed positioning.
+  adjustForDevice : (y, v) ->
+    if browser.is_mobile
+      # When absolutely positioning, the reference is with respect to the closest
+      # parent that has been positioned. Because sticky.y is with respect to the
+      # document, we need to adjust for the parent offset.
+      y -= v.$el.parent().offsetParent().offset().top
+      x = 0
+    else
+      # Fixed positioning is relative to the viewport, not the document
+      y -= scroller.viewport_top
+
+      # Adjust for horizontal scroll for fixed position elements because they don't
+      # move with the rest of the content (they're fixed to the viewport).
+      # ScrollLeft is used to offset the fixed element to simulate sticking to the window.
+      x = -$(window).scrollLeft()
+
+    [x,y]
+

   ########
   # toggleStuck
@@ -246,7 +265,7 @@
     is_stuck = !sticky.stuck
     sticky.stuck = is_stuck
     if !is_stuck
-      sticky.y = null
+      sticky.y = sticky.x = null
     save sticky

     if v.stuck_key?
@@ -298,17 +317,11 @@
   #
   # Different constraints may need to be introduced to accommodate different
   # sticky component configurations.
-  solveForY : (stickies) ->
+  solveForY : (stuck, stickies) ->

     # cassowary constraint solver
     solver = new c.SimplexSolver()

-    # Stores the cassowary variables representing each component's y-position
-    # that the solver will optimize.
-    y_pos = {}
-    for own k,v of stickies
-      y_pos[k] = new c.Variable
-
     # We modify the contraints slightly based on whether we're scrolling up or down
     scrolling_down = scroller.viewport_top > scroller.last_viewport_top
     scroll_distance = Math.abs(scroller.viewport_top - scroller.last_viewport_top)
@@ -318,7 +331,14 @@
     # We'll iterate through each component in order of their stacking priority
     y_stack = 0

-    sorted = _.sortBy(_.values(stickies), (v) -> v.stack_priority)
+    sorted = (v for own k,v of stickies when k in stuck)
+    sorted = _.sortBy( sorted, (v) -> v.stack_priority)
+
+    # Stores the variables representing each component's y-position
+    # that the cassowary will optimize.
+    y_pos = {}
+    for v in sorted
+      y_pos[v.key] = new c.Variable

     ########
     # Linear constraints
@@ -383,17 +403,17 @@

       if sticky.y?
         # BOUNDED BY SCROLL DISTANCE
-        console.log "\tBOUNDED BY SCROLL DISTANCE: |#{k} - #{sticky.y}| <= |#{scroller.viewport_top} - #{scroller.last_viewport_top}|, strong" if debug
+        console.log "\tBOUNDED BY SCROLL DISTANCE: |#{k} - #{scroller.last_positions[k]}| <= |#{scroller.viewport_top} - #{scroller.last_viewport_top}|, strong" if debug
         solver.addConstraint new c.Inequality \
           y_pos[k], \
           c.LEQ,
-          sticky.y + scroll_distance, \
+          scroller.last_positions[k] + scroll_distance, \
           c.Strength.strong

         solver.addConstraint new c.Inequality \
           y_pos[k], \
           c.GEQ,
-          sticky.y - scroll_distance, \
+          scroller.last_positions[k] - scroll_distance, \
           c.Strength.strong

       # CLOSE TO TOP


use proper octal syntax (thanks to aidanhs for finding it)

--- code/serv.coffee
+++ code/serv.coffee
@@ -121,7 +121,8 @@
           "#{key.key}"

         fs.writeFileSync keys_path, keys.join '\n', 'utf8'
-        fs.chmodSync keys_path, (parseInt '0600', 8)
+        # Note: octal.  This is deliberate.
+        fs.chmodSync keys_path, 0o600
         child_process.exec "chown #{box.name}: #{keys_path}" # insecure
         res.send {"status": "ok"}


less tracking

less tracking

--- coffeeApp.coffee
+++ coffeeApp.coffee
@@ -74,7 +74,7 @@

-stream = Twitter.stream 'statuses/filter', {track:'fuck,fucker,fucking,fucked,fucks', location:'-180,-90,180,90'}
+stream = Twitter.stream 'statuses/filter', {track:'fuck,fucking,fucks'}

 id = 0
 # Logic to get tweets


Tab length 4 for c source.

Tab length 4 for c source.

--- env/atom.config.cson
+++ env/atom.config.cson
@@ -25,6 +25,7 @@
 			"rainbow-selection"
 			"wrap-guide"
 			"opened-files"
+			"linter"
 		]
 		themes: [
 			"isotope-light-ui"
@@ -146,6 +147,10 @@
 	editor:
 		softTabs: true
 		tabLength: 2
+".c.source":
+	editor:
+		softTabs: true
+		tabLength: 4
 ".coffee.source":
 	editor:
 		tabLength: 2


Wirepatternize orientation component

Wirepatternize orientation component

--- components/GetOrientation.coffee
+++ components/GetOrientation.coffee
@@ -2,30 +2,30 @@

 exports.getComponent = ->
   c = new noflo.Component
+  c.icon = 'file-image-o'
   c.description = 'Get orientation from image dimensions'

   c.inPorts.add 'dimensions',
     datatype: 'object'
-    process: (event, packet) ->
-      switch event
-        when 'begingroup'
-          c.outPorts.orientation.beginGroup packet
-        when 'data'
-          return unless packet.width or packet.height
-          orientation = 'square'
-          if packet.width > packet.height
-            orientation = 'landscape'
-          if packet.width < packet.height
-            orientation = 'portrait'
-          c.outPorts.orientation.send
-            orientation: orientation
-        when 'endgroup'
-          c.outPorts.orientation.endGroup()
-        when 'disconnect'
-          c.outPorts.orientation.disconnect()

   c.outPorts.add 'orientation',
     datatype: 'object'

+  noflo.helpers.WirePattern c,
+    in: 'dimensions'
+    out: 'orientation'
+    forwardGroups: true
+    async: true
+  , (packet, groups, out, callback) ->
+    return callback new Error "Dimension is missing width" unless packet.width
+    return callback new Error "Dimension is missing height" unless packet.height
+    orientation = 'square'
+    if packet.width > packet.height
+      orientation = 'landscape'
+    if packet.width < packet.height
+      orientation = 'portrait'
+    out.send
+      orientation: orientation
+    do callback
+
   c
-


default to clean urls in development

default to clean urls in development

--- root/app.coffee
+++ root/app.coffee
@@ -21,3 +21,6 @@

   jade:
     pretty: true
+
+  server:
+    clean_urls: true


Auto-reformatted config (?)

Auto-reformatted config (?)

--- etc/atom/config.cson
+++ etc/atom/config.cson
@@ -1,29 +1,27 @@
-'exception-reporting':
-  'userId': 'f87332bf-54f7-bc72-aeba-5b58d78cb5d4'
-'release-notes':
-  'viewedVersion': '0.95.0'
-'welcome':
-  'showOnStartup': false
-'metrics':
-  'userId': '305d8eb962055d82cd69f9974fcc3c7d0e3f288e'
-'editor':
-  'fontFamily': 'Source Code Pro'
-  'showIndentGuide': true
-  'softWrapAtPreferredLineLength': true
-  'fontSize': 14
-  'invisibles': {}
-'core':
-  'themes': [
-    'atom-dark-ui'
-    'solarized-dark-syntax'
-  ]
-  'projectHome': '/Users/stillers/workspace'
-'markdown-preview': {}
-'whitespace':
-  'removeTrailingWhitespace': false
-'bracket-matcher':
-  'autocompleteBrackets': false
-'find-and-replace':
-  'focusEditorAfterSearch': true
-'tabs': {}
-'docblockr': {}
+'global':
+  'exception-reporting':
+    'userId': 'f87332bf-54f7-bc72-aeba-5b58d78cb5d4'
+  'release-notes':
+    'viewedVersion': '0.95.0'
+  'welcome':
+    'showOnStartup': false
+  'metrics':
+    'userId': '305d8eb962055d82cd69f9974fcc3c7d0e3f288e'
+  'editor':
+    'fontFamily': 'Source Code Pro'
+    'showIndentGuide': true
+    'softWrapAtPreferredLineLength': true
+    'fontSize': 14
+    'invisibles': {}
+  'core':
+    'themes': [
+      'atom-dark-ui'
+      'solarized-dark-syntax'
+    ]
+    'projectHome': '/Users/stillers/workspace'
+  'whitespace':
+    'removeTrailingWhitespace': false
+  'bracket-matcher':
+    'autocompleteBrackets': false
+  'find-and-replace':
+    'focusEditorAfterSearch': true


Added buildtask snippet

Added buildtask snippet

--- completions.cson
+++ completions.cson
@@ -656,6 +656,49 @@
     }
   ]

+'Class BuildTask Silverstripe':
+  'prefix': 'classtask'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * ${3:Description}
+   *
+   * @package silverstripe
+   * @subpackage ${4:mysite}
+   */
+  class ${1} extends BuildTask
+  {
+  \t/**
+  \t * @var bool $enabled If set to FALSE, keep it from showing in the list
+  \t * and from being executable through URL or CLI.
+  \t */
+  \tprotected $enabled = true;
+  \t
+  \t /**
+  \t * @var string $title Shown in the overview on the TaskRunner
+  \t * HTML or CLI interface. Should be short and concise, no HTML allowed.
+  \t */
+  \tprotected $title = \'${2:${1}}\';
+  \t
+  \t /**
+  \t * @var string $description Describe the implications the task has,
+  \t * and the changes it makes. Accepts HTML formatting.
+  \t */
+  \tprotected $description = \'${3}\';
+
+  \t/**
+  \t * This method called via the TaskRunner
+  \t *
+  \t * @param SS_HTTPRequest $request
+  \t */
+  \tpublic function run(SS_HTTPRequest $request)
+  \t{
+  \t\t${0}
+  \t}
+  }
+  """
+
 'menu_title':
   'prefix': 'menu_title'
   'module': 'silverstripe/framework'


Simplest way to check bottom of page

Simplest way to check bottom of page

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -20,11 +20,7 @@

   bottomPage: ->
-    pageBottom = document.getElementsByClassName('js-page-footer')[0]
-      .getBoundingClientRect()
-      .bottom
-
-    pageBottom == window.innerHeight
+    document.body.clientHeight == (document.body.scrollHeight - document.body.scrollTop)

   ajaxError: (xhr) ->


Cleanup debugging stuff.

Cleanup debugging stuff.

--- app/assets/javascripts/app/tasks.js.coffee
+++ app/assets/javascripts/app/tasks.js.coffee
@@ -11,8 +11,4 @@
 todolist.on 'sortupdate', (event, ui) ->
   task = $(ui.item)
   position = task.parent().children().index(ui.item)
-
-  console.log(task.data("id"))
-  $.post '/tasks/'+task.data("id")+'/update_position', {position: position+1}
-  console.log(ui.item)
-  console.log(position)+  $.post '/tasks/'+task.data("id")+'/update_position', {position: position+1}


Changed the registration of functions from global scope to local scope due namespace pollution;

--- app/assets/javascripts/client/index.coffee
+++ app/assets/javascripts/client/index.coffee
@@ -87,7 +87,7 @@
     @variants.push(options)
     this

-  control: (name = 'Control', options, callback) =>
+  control: (name = 'Control', options, callback) ->
     if typeof options isnt 'object'
       callback = options
       options  = {}
@@ -95,13 +95,13 @@
     options.control = true
     @variant(name, options, callback)

-  continue: =>
+  continue: ->
     # Use the same variant as before, don't record anything
     if variant = @getPreviousVariant()
       @useVariant(variant)
     this

-  start: (name, options = {}) =>
+  start: (name, options = {}) ->
     if variant = @getPreviousVariant()
       # Use the same variant as before, don't record anything
       @useVariant(variant)
@@ -130,7 +130,7 @@
     @useVariant(variant)
     this

-  complete: (name) =>
+  complete: (name) ->
     # Optionally pass a name, or read from the cookie
     name or= @getVariantCookie()
     return this unless name
@@ -147,7 +147,7 @@
     @recordComplete(name)
     this

-  reset: =>
+  reset: ->
     @removeVariantCookie()
     @removePersistCompleteCookie()
     @result = null
@@ -155,14 +155,14 @@

   # Private

-  getVariantForName: (name) =>
+  getVariantForName: (name) ->
     (v for v in @variants when v.name is name)[0]

-  useVariant: (variant) =>
+  useVariant: (variant) ->
     variant?.callback?()
     @chosen = variant

-  recordStart: (variant) =>
+  recordStart: (variant) ->
     # Record which experiment was run on the server
     request(
       "#{@endpoint}/start",
@@ -174,34 +174,34 @@
     # Set the variant we chose as a cookie
     @setVariantCookie(variant.name)

-  recordComplete: (name) =>
+  recordComplete: (name) ->
     # Record the experiment was completed on the server
     request("#{@endpoint}/complete", experiment: @name, variant: name)

   # Variant Cookie

-  getPreviousVariant: =>
+  getPreviousVariant: ->
     if name = @getVariantCookie()
       @getVariantForName(name)

-  getVariantCookie: =>
+  getVariantCookie: ->
     @getCookie("abbaVariant_#{@name}")

-  setVariantCookie: (value) =>
+  setVariantCookie: (value) ->
     @setCookie("abbaVariant_#{@name}", value, expires: @options.expires)

-  removeVariantCookie: =>
+  removeVariantCookie: ->
     @setCookie("abbaVariant_#{@name}", '', expires: true)

   # Complete Cookie

-  setPersistCompleteCookie: =>
+  setPersistCompleteCookie: ->
     @setCookie("abbaPersistComplete_#{@name}", '1', expires: @options.expires)

-  hasPersistCompleteCookie: =>
+  hasPersistCompleteCookie: ->
     !!@getCookie("abbaPersistComplete_#{@name}")

-  removePersistCompleteCookie: =>
+  removePersistCompleteCookie: ->
     @setCookie("abbaPersistComplete_#{@name}", '', expires: true)

   setCookie: (name, value, options = {}) ->


Set "nonInteraction: true"

Set "nonInteraction: true"

--- app/assets/javascripts/ga_events.js.coffee
+++ app/assets/javascripts/ga_events.js.coffee
@@ -61,13 +61,16 @@
     window.dataLayer.push data

 class GaEvents.GoogleUniversalAnalyticsAdapter
-  push: (h) -> window.ga "send", "event", h.category, h.action, h.label, h.value
+  push: (h) ->
+    window.ga "send", "event", h.category, h.action, h.label, h.value,
+              {"nonInteraction": true}

 class GaEvents.GoogleAnalyticsAdapter
   # Send events non_interactive => no influence on bounce rates
   push: (h) ->
     window._gaq.push(
-      ["_trackEvent", h.category, h.action, h.label, h.value, true])
+      ["_trackEvent", h.category, h.action, h.label, h.value, true]
+    )

 class GaEvents.NullAdapter
   push: (obj) -> console.log obj if console?


Only attempt to load when the button is pressed.

--- app/assets/javascripts/iframe-saver.coffee
+++ app/assets/javascripts/iframe-saver.coffee
@@ -8,9 +8,7 @@
     @post_url = @$save_button.data('postBackUrl')
     @get_url  = @$revert_button.data('postBackUrl')

-    @iframePhone = new Lab.IFramePhone @$frame, =>
-        @load_interactive()
-      , =>
+    @iframePhone = new Lab.IFramePhone @$frame, null, =>
         console.log  "IFramePone: systems go"
         @iframePhone.addListener 'interactiveState', (interactive_json) =>
           @interactive = interactive_json
@@ -49,6 +47,7 @@
           @interactive = JSON.parse(response['raw_data'])
           url = @interactive.models[0].url
           @interactive.models[0].url = "http://lab.concord.org/#{url}"
+          @iframePhone.post({ type:'loadInteractive', data:@interactive  });
           alert "we loaded our interactive"

       error: (jqxhr, status, error) =>


Fix bug with add one popup content each time popup is opened.

--- app/assets/javascripts/views/map.js.coffee
+++ app/assets/javascripts/views/map.js.coffee
@@ -94,10 +94,10 @@
     @set('isPopping', true)

     Ember.run.once ()->
-      #popupView.createElement()
-      #Wheelmap.ViewHelper.enterDom(popupView)
-      #popup.setContent(popupView.get('element'))
-      popupView.appendTo(popup._contentNode)
+      # @TODO remove this ugly hacks (eg. enterDom, exitDom) and find a way to better extend leaflet here
+      popupView.append() # Create it now, move it later
+      Ember.run.scheduleOnce 'render', popupView, '_insertElement', ()->
+        $(popupView.get('element')).appendTo(popup._contentNode)

     Ember.run.once ()->
       popup._adjustPan()
@@ -107,7 +107,6 @@

     Ember.run.once ()->
       popupView.remove()
-     # Wheelmap.ViewHelper.exitDom(popupView)

   didClosePopup: (event)->
     @set('isPopping', false)


Fixed checkout monitoring in analytics

Fixed checkout monitoring in analytics

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -85,22 +85,6 @@

         @win.ga("send", "event", category, action, label, value)

-    ecStep: (step) ->
-        return if not @.initialized
-        return if not @win.ga
-
-        if step == "register"
-            stepId = 1
-        else if step == "change-plan"
-            stepId = 2
-        else if step == "select-plan"
-            stepId = 3
-        else if step == "confirm-plan"
-            stepId = 4
-        else if step == "plan-changed"
-            stepId = 5
-
-
     ecViewPlan: (plan) ->
         return if not @.initialized
         return if not @win.ga
@@ -171,13 +155,24 @@
             'quantity': 1,
             'position': 1,
         })
-        @.ecStep("confirm-plan")
+        @win.ga('ec:setAction','checkout', {'step': 1,})
+        @.trackEvent("ecommerce", "start-checkout", step, stepId)

     ecPurchase: (plan_id, plan_name, plan_price) ->
         return if not @.initialized
         return if not @win.ga

-        @.ecStep("plan-changed")
+        @win.ga('ec:addProduct', {
+            'id': plan_id,
+            'name': plan_name,
+            'price': plan_price,
+            'category': "plans",
+            'quantity': 1,
+            'position': 1,
+        })
+
+        @win.ga('ec:setAction','checkout', {'step': 2,})
+        @.trackEvent("ecommerce", "start-checkout", step, stepId)

         @win.ga('ec:addProduct', {
             'id': plan_id,


Remove null milestone filters data requests

--- app/coffee/modules/controllerMixins.coffee
+++ app/coffee/modules/controllerMixins.coffee
@@ -244,7 +244,6 @@
             loadFilters[key] = urlfilters[key]
             loadFilters[excludeKey] = urlfilters[excludeKey]

-        loadFilters.milestone = "null"
         if milestone
             loadFilters.milestone = milestone


getSignUpValidationErrors function refactored

--- assets/controllers/shopCtrl.coffee
+++ assets/controllers/shopCtrl.coffee
@@ -1,29 +1,44 @@
+ShopFunctions = (() ->
+
+  _getSignUpValidationErrors = ($scope) ->
+
+    validationErrors = []
+
+    # Verify password and passwordRpt match
+    if $scope.password != $scope.passwordRpt
+      validationErrors.push('Passwords do not match')
+
+    if !$scope.shopname
+      validationErrors.push('A shop name has to be provided')
+
+    if !$scope.email
+      validationErrors.push('An email has to be provided')
+
+    if !$scope.password
+      validationErrors.push('A password has to be provided')
+
+    validationErrors
+
+
+  getSignUpValidationErrors: _getSignUpValidationErrors
+)()
+
 # Shop SignUp
 angular.module("mean.system").controller "ShopSignUpController",
   ["$scope", "$http", "$location", "Global", ($scope, $http, $location, Global) ->
+
     $scope.global = Global
     $scope.errors = null

     $scope.doSignUp = () ->

-      validationErrors = []
-
-      # Verify password and passwordRpt match
-      if $scope.password != $scope.passwordRpt
-        validationErrors.push('Passwords do not match')
-
-      if !$scope.shopname
-        validationErrors.push('A shop name has to be provided')
-
-      if !$scope.email
-        validationErrors.push('An email has to be provided')
-
-      if !$scope.password
-        validationErrors.push('A password has to be provided')
+      validationErrors = ShopFunctions.getSignUpValidationErrors($scope)

       if validationErrors.length > 0
         $scope.errors = validationErrors
+
       else
+
         $http(
           url: "/shop/signup"
           method: "POST"
@@ -33,12 +48,9 @@
             password: $scope.password
         )
         .success () ->
-
-            $location.path('/shop/offers')
-
-        .error (data) ->
-
-            $scope.errorMsg = data
+          $location.path('/shop/offers')
+        .error (errorData) ->
+          $scope.errorMsg = errorData
   ]

 # Shop LogIn


make function name more explicit

make function name more explicit

--- app/controllers/passport_controller.coffee
+++ app/controllers/passport_controller.coffee
@@ -11,7 +11,7 @@
 # or not.
 action 'isAuthenticated', ->

-    answer = (err, users) ->
+    checkUserExistence = (err, users) ->
         if err
             console.log err
             send error: true, nouser: false, 500
@@ -21,7 +21,7 @@
             send error: true, nouser: true

     if not req.isAuthenticated()
-        User.all answer
+        User.all checkUserExistence
     else
         send success: true, msg: "User is authenticated."


Improve exception messages from remote calls

Spent a while tracking down `Error processing argument -1`, caused by a missing param (`app.exit()` now takes an exit code.) Improve the rpc-server so that it prints the function name when possible, so it's much easier to identify which remote call is causing the error.

--- atom/browser/lib/rpc-server.coffee
+++ atom/browser/lib/rpc-server.coffee
@@ -103,13 +103,25 @@
 # Call a function and send reply asynchronously if it's a an asynchronous
 # style function and the caller didn't pass a callback.
 callFunction = (event, func, caller, args) ->
-  if v8Util.getHiddenValue(func, 'asynchronous') and typeof args[args.length - 1] isnt 'function'
-    args.push (ret) ->
+  funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
+  funcPassedCallback = args[args.length - 1] is 'function'
+
+  try
+    if funcMarkedAsync and not funcPassedCallback
+      args.push (ret) ->
+        event.returnValue = valueToMeta event.sender, ret, true
+      func.apply caller, args
+    else
+      ret = func.apply caller, args
       event.returnValue = valueToMeta event.sender, ret, true
-    func.apply caller, args
-  else
-    ret = func.apply caller, args
-    event.returnValue = valueToMeta event.sender, ret, true
+  catch e
+    # Catch functions thrown further down in function invocation and wrap
+    # them with the function name so it's easier to trace things like
+    # `Error processing argument -1.`
+    funcName = func.name ? "anonymous"
+    throw new Error("Could not call remote function `#{funcName}`.
+                     Check that the function signature is correct.
+                     Underlying error: #{e.message}")

 # Send by BrowserWindow when its render view is deleted.
 process.on 'ATOM_BROWSER_RELEASE_RENDER_VIEW', (id) ->


Update spec naming

Update spec naming

--- spec/buffer-search-spec.coffee
+++ spec/buffer-search-spec.coffee
@@ -3,7 +3,7 @@
 FindOptions = require '../lib/find-options'

 describe "BufferSearch", ->
-  [search, editor, markersListener, currentResultListener] = []
+  [model, editor, markersListener, currentResultListener] = []

   beforeEach ->
     editor = new TextEditor
@@ -22,26 +22,24 @@
     """

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

     markersListener = jasmine.createSpy('markersListener')
-    search.onDidUpdate(markersListener)
+    model.onDidUpdate(markersListener)

     currentResultListener = jasmine.createSpy('currentResultListener')
-    search.onDidChangeCurrentResult(currentResultListener)
-
-    search.setEditor(editor)
+    model.onDidChangeCurrentResult(currentResultListener)
+
+    model.setEditor(editor)
     markersListener.reset()

-    search.setFindOptions(
-      findPattern: "a+"
+    model.search "a+",
       caseSensitive: false
       useRegex: true
       wholeWord: false
-    )

   afterEach ->
-    search.destroy()
+    model.destroy()
     editor.destroy()

   getHighlightedRanges = ->
@@ -382,7 +380,7 @@
       expect(currentResultListener).toHaveBeenCalledWith(markers[1])
       currentResultListener.reset()

-      search.replace([markers[1]], "new-text")
+      model.replace([markers[1]], "new-text")

       expect(editor.getText()).toBe """
         -----------


:lipstick: Add minimap visual bookmarks in project tests


app/flux: add getters for draft stack templates

--- client/app/lib/flux/environment/getters.coffee
+++ client/app/lib/flux/environment/getters.coffee
@@ -163,6 +163,13 @@
   (templates) -> templates.filter (t) -> t.get('inUse')
 ]

+draftStackTemplates = [
+  teamStackTemplates
+  privateStackTemplates
+  (teamTemplates, privateTemplates) ->
+    teamTemplates.merge(privateTemplates).filter (t) -> not t.get('inUse')
+]
+
 module.exports = {
   stacks
   ownMachines
@@ -184,4 +191,5 @@
   privateStackTemplates
   inUseTeamStackTemplates
   inUsePrivateStackTemplates
+  draftStackTemplates
 }


fix typo

fix typo


Update CodeQuest rankings slightly after recategorizing a few players' brackets


add tolerance to cursor tests for different browser renderers

--- tests/scripts/unit/cursor.coffee
+++ tests/scripts/unit/cursor.coffee
@@ -23,61 +23,73 @@
       cursorManager.setCursor('id', 2, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('17px')
-      expect(cursor.style.top).to.equal('0px')
+      expect(parseInt(cursor.style.top)).to.be.gte(0)
+      expect(parseInt(cursor.style.top)).to.be.lte(1)
     )

     it('should set at leaf boundary', ->
       cursorManager.setCursor('id', 4, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('29px')
-      expect(cursor.style.top).to.equal('0px')
+      expect(parseInt(cursor.style.top)).to.be.gte(0)
+      expect(parseInt(cursor.style.top)).to.be.lte(1)
     )

     it('should set at newline', ->
       cursorManager.setCursor('id', 11, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('0px')
-      expect(cursor.style.top).to.equal('15px')
+      expect(parseInt(cursor.style.top)).to.be.gte(15)
+      expect(parseInt(cursor.style.top)).to.be.lte(16)
     )

     it('should set at beginning', ->
       cursorManager.setCursor('id', 0, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('0px')
-      expect(cursor.style.top).to.equal('0px')
+      expect(parseInt(cursor.style.top)).to.be.gte(0)
+      expect(parseInt(cursor.style.top)).to.be.lte(1)
     )

     it('should set at end', ->
       cursorManager.setCursor('id', 17, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('46px')
-      expect(cursor.style.top).to.equal('35px')
+      expect(parseInt(cursor.style.top)).to.be.gte(34)
+      expect(parseInt(cursor.style.top)).to.be.lte(35)
     )

     it('should set at beginning of large font', ->
       cursorManager.setCursor('id', 12, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('0px')
-      expect(cursor.style.top).to.equal('30px')
+      expect(parseInt(cursor.style.top)).to.be.gte(29)
+      expect(parseInt(cursor.style.top)).to.be.lte(31)
       inner = cursor.querySelector('.cursor-inner')
-      expect(inner.style.height).to.equal('21px')
+      expect(parseInt(inner.style.height)).to.be.gte(19)
+      expect(parseInt(inner.style.height)).to.be.lte(21)
     )

     it('should set in middle of large font', ->
       cursorManager.setCursor('id', 14, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('20px')
-      expect(cursor.style.top).to.equal('30px')
+      expect(parseInt(cursor.style.top)).to.be.gte(29)
+      expect(parseInt(cursor.style.top)).to.be.lte(31)
       inner = cursor.querySelector('.cursor-inner')
-      expect(inner.style.height).to.equal('21px')
+      expect(parseInt(inner.style.height)).to.be.gte(19)
+      expect(parseInt(inner.style.height)).to.be.lte(21)
     )

     it('should set in end of large font', ->
       cursorManager.setCursor('id', 17, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('46px')
-      expect(cursor.style.top).to.equal('35px')
+      expect(parseInt(cursor.style.top)).to.be.gte(34)
+      expect(parseInt(cursor.style.top)).to.be.lte(35)
       inner = cursor.querySelector('.cursor-inner')
+      expect(parseInt(inner.style.height)).to.be.gte(19)
+      expect(parseInt(inner.style.height)).to.be.lte(21)
     )
   )
 )


Drops from the filesystem prompt for node titles too.


fixes and talked to io server

fixes and talked to io server


Prevent spell error marker adding newly typed characters

--- services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
@@ -31,6 +31,10 @@
 				row: options.row,
 				column: options.column + options.word.length
 			})
+			# Prevent range from adding newly typed characters to the end of the word.
+			# This makes it appear as if the spelling error continues to the next word
+			# even after a space
+			range.end.$insertRight = true

 			markerId = session.addMarker range, "spelling-highlight", 'text', false


Reversion and update -- fixes broken tests, bug

--- javascripts/widgets/ui_elements.coffee
+++ javascripts/widgets/ui_elements.coffee
@@ -1,8 +1,9 @@
 #= require almond_wrapper
 #= require lib
 #= require jquery
+#= require widgets/mathml

-ttm.define 'widgets/ui_elements', ['lib/class_mixer'], (class_mixer)->
+ttm.define 'widgets/ui_elements', ['lib/class_mixer', 'widgets/mathml'], (class_mixer, mathml_renderer)->
   class Button
     initialize: (@opts={})->
     render: (opts={})->
@@ -38,9 +39,6 @@
       opts.element.append @figure
       @figure

-    default: ->
-      @wrappedMathTag("")
-
     wrappedMathTag: (content)->
       """
       <math xmlns=\"http://www.w3.org/1998/Math/MathML\">
@@ -49,10 +47,7 @@
       """

     update: (mathml)->
-      mathml_in_tag = @wrappedMathTag(mathml)
-      elem = MathJax.Hub.getAllJax(@figure[0])[0]
-      if elem
-        MathJax.Hub.Queue(["Text", elem, mathml_in_tag])
+      @mathml_renderer.render(@figure, @wrappedMathTag(mathml))

   class_mixer(MathMLDisplay)


add facebook and twitter auth routes to gravity proxy

--- lib/middleware/proxy_to_gravity.coffee
+++ lib/middleware/proxy_to_gravity.coffee
@@ -15,7 +15,7 @@
   '/oauth2*', '/robots.txt', '/humans.txt', '/sitemap*', "/auction-registration/*", '/assets/common-e1*.js', '/unsubscribe*'
 ]
 AUTH_ROUTES = [
-  '/post', "/users/sign_in", "/users/sign_out", "/user/delete", "/user/edit", "/profile/edit"
+  '/post', "/users/sign_in", "/users/sign_out", "/user/delete", "/user/edit", "/profile/edit", "/users/auth/facebook", "/users/auth/twitter"
 ]
 for route in ROUTES
   app.all route, (req, res) ->
@@ -27,4 +27,4 @@
     proxy.web req, res, { target: ARTSY_URL }

 module.exports.api = (req, res) ->
-  proxy.web req, res, { target: ARTSY_URL }+  proxy.web req, res, { target: ARTSY_URL }


Ground Truth added to chart

Ground Truth added to chart

--- site/app.coffee
+++ site/app.coffee
@@ -107,6 +107,7 @@
   'epiweek': 201201 + 100 * Math.floor(i / 52) + i % 52
   'value': 1 + Math.random() * 3
   'std': 0.5 + Math.random() * 1
+  'wili': 1 + Math.random() * 3

 getEpidataHander = (callback) ->
   return (result, message, epidata) ->
@@ -116,6 +117,15 @@
       msg = "The Epidata API says '#{message}'. (error ##{result})"
       console.log(msg)
       alert.log(msg)
+
+Epidata_fluview_single = (handler, location, epiweeks) ->
+  if Epidata?.fluview?
+    Epidata.fluview(handler, location, epiweeks)
+  else
+    fakeData = (getFakeRow(location, i) for i in [0...280])
+    callback = () -> handler(1, 'debug', fakeData)
+    delay = 250 + Math.round(Math.random() * 500)
+    window.setTimeout(callback, delay)

 Epidata_nowcast_single = (handler, location) ->
   if Epidata?.nowcast?
@@ -460,10 +470,6 @@
       ctx.translate(-wt / 2, +ht / 2)
       ctx.fillText(txt, 0, 0)
       ctx.restore()
-    # ILI over time
-    iVals = (i for i in [0...numWeeks])
-    iliVals = (@chartData[i].value for i in [0...numWeeks])
-    trace(iVals.map(i2x), iliVals.map(ili2y))
     # axes
     trace([0, numWeeks - 1].map(i2x), [0, 0].map(ili2y))
     trace([0, 0].map(i2x), [0, maxILI].map(ili2y))
@@ -489,6 +495,14 @@
         yr = ('00' + yr).slice(-2)
         wk = ('00' + wk).slice(-2)
         write("'#{yr}w#{wk}", x, h - 2 * padding.bottom / 3, -0.125)
+    # ILI over time
+    iVals = (i for i in [0...numWeeks])
+    if @truthData?
+      iliVals = (@truthData[i].wili for i in [0...(numWeeks-38)])
+      trace(iVals.map(i2x), iliVals.map(ili2y))
+    ctx.strokeStyle = '#FF0000'
+    iliVals = (@chartData[i].value for i in [0...numWeeks])
+    trace(iVals.map(i2x), iliVals.map(ili2y))
     # title
     ctx.font = 24 * Math.min(1, w / 500) + 'px sans-serif'
     name = NAMES[@chartData[0].location]
@@ -522,11 +536,17 @@

   fetchNowcast: (loc) ->
     @chartData = null
+    @truthData = null
     callback = (epidata) => @onNowcastReceived(epidata)
     Epidata_nowcast_single(getEpidataHander(callback), loc)

   onNowcastReceived: (epidata) ->
     current = epidata[epidata.length - 1]
+    start = epidata[0]
+    loc = current.location
+    if loc in REGIONS
+      callback = (ilidata) => @onFluviewReceived(ilidata)
+      Epidata_fluview_single(getEpidataHander(callback), loc, start.epiweek+ "-" + current.epiweek)
     ili = '' + (Math.round(current.value * 100) / 100)
     if '.' in ili
       ili += '00'
@@ -534,7 +554,6 @@
       ili = ili.slice(0, idx + 3)
     else
       ili += '.00'
-    loc = current.location
     epiweek = current.epiweek
     @chartData = epidata
     $('#nowcast_label').text("ILI nowcast for #{loc} as of #{epiweek}:")
@@ -543,3 +562,7 @@
     $('.location_right').css('display', 'block')
     $('#loading_icon').css('display', 'none')
     @resizeCanvas()
+
+  onFluviewReceived: (ilidata) ->
+    @truthData = ilidata
+    @resizeCanvas()


added tests for initialize

added tests for initialize

--- _src/test/main.coffee
+++ _src/test/main.coffee
@@ -6,6 +6,38 @@
 METRICKEYS = ["t", "g_cpu", "g_mem", "g_memtotal", "p_id", "p_uptime", "p_mem", "p_cpu"]

 describe "----- Module TESTS -----", ->
+
+	describe 'Create Instance', ->
+
+		it "without any options", ( done )->
+			_hb = new Heartbeat( autostart: false )
+			_hb.on "error", ( _err )->
+				_err.name.should.equal( "ENONAME" )
+				done()
+				return
+			_hb.start()
+			return
+
+		it "without identifier option", ->
+			_hb = new Heartbeat( { name: "FAIL", autostart: false } )
+			_hb.on "error", ( _err )->
+				_err.name.should.equal( "ENOIDENTIFIER" )
+				done()
+				return
+			_hb.start()
+			return
+
+		it "without name option", ->
+			_hb = new Heartbeat( { identifier: "explode", autostart: false } )
+			_hb.on "error", ( _err )->
+				_err.name.should.equal( "ENONAME" )
+				done()
+				return
+			_hb.start()
+			return
+
+		return
+

 	describe 'Main Tests', ->
 		_hb = null


<C-Enter> in debugger steps into instead of over

--- client/editor.coffee
+++ client/editor.coffee
@@ -49,7 +49,7 @@
   k = # extra keys for CodeMirror
     Tab: -> c = cm.getCursor(); opts.autocomplete? cm.getLine(c.line), c.ch; return
     Enter: -> (if opts.debugger then opts.over?() else cm.execCommand 'newlineAndIndent'); return
-    'Ctrl-Enter': -> (if opts.debugger then opts.over?()); return
+    'Ctrl-Enter': -> (if opts.debugger then opts.into?()); return
   ###
     F6: -> opts.openInExternalEditor? cm.getValue(), cm.getCursor().line, (err, text) ->
       if err then $.alert '' + err, 'Error' else c = cm.getCursor().line; cm.setValue text; cm.setCursor c


Bugfix.

Bugfix.

--- client/upload.coffee
+++ client/upload.coffee
@@ -14,7 +14,7 @@
   console.log person
   return unless person
   Publications.find
-    _id: {$in: person.library}
+    _id: {$in: person.library or []}
     importing:
       $exists: false


Fixed name of color to RGB method.

Fixed name of color to RGB method.

--- coffee/figure.coffee
+++ coffee/figure.coffee
@@ -49,7 +49,7 @@
         @renderer = new THREE.WebGLRenderer()
         @renderer.setSize(@properties.width, @properties.height)
         # @renderer.setClearColor(@properties.bgcolor)
-        @renderer.setClearColor(@properties.color.intRBG())
+        @renderer.setClearColor(@properties.color.intRGB())
         @container.append(@renderer.domElement)

         # scene


Add Sentry error logging to server

Add Sentry error logging to server

--- coffee/server.coffee
+++ coffee/server.coffee
@@ -1,6 +1,7 @@
 "use strict"

 express = require 'express'
+raven   = require 'raven'
 MongoClient = require('mongodb').MongoClient

 collection = require './collection'
@@ -33,6 +34,7 @@
 app.enable('verbose errors')
 app.set 'port', process.env.PORT_WWW or 8080
 app.use(app.router)
+app.use(raven.middleware.express(process.env.SENTRY_DNS)) if process.env.SENTRY_DNS

 # Error Handler
 app.use (err, req, res, next) ->


Added support for 9gag's latest DOM format for animated images.

--- src/9gag.coffee
+++ src/9gag.coffee
@@ -50,14 +50,12 @@
       d.innerHTML = img_title
       img_title = d.childNodes[0].nodeValue

-      img_src = Select(handler.dom, ".badge-item-animated-img")[0]
+      img_src = Select(handler.dom, ".badge-animated-container-animated")[0]

       if not img_src
         img_src = Select(handler.dom, ".badge-item-img")[0]
-
-      img_src = img_src.attribs.src
-
-      if img_src.substr(0, 4) != "http"
-        img_src = "http:#{img_src}"
+        img_src = img_src.attribs.src
+      else
+        img_src = img_src.attribs['data-image']

       msg.send img_title, img_src


Cleanup whitespace

Cleanup whitespace

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemFollow.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemFollow.coffee
@@ -2,7 +2,7 @@
   cssClassMap = ->
     JTag      : "topic"
     JAccount  : "account"
-
+
   constructor:(options,data)->
     options = $.extend options,
       cssClass : "follow bucket #{cssClassMap()[data.sourceName]}"
@@ -11,15 +11,15 @@
     @anchor = new ProfileLinkView {
       origin: data.anchor
     }
-
+
     @group = new LinkGroup {
       group         : data.group
       itemsToShow   : 3
       subItemClass  : options.subItemLinkClass
     }
-
+
     @getData().on 'ItemWasAdded', -> log 'heres the event, sinan', arguments
-
+
   pistachio:->
     """

@@ -27,11 +27,11 @@
     followed
     {{> @group}}
     """
-
+
   render:->
-
+
   addCommentBox:->
-
+
   viewAppended:->
     @setTemplate @pistachio()
     @template.update()
@@ -45,15 +45,15 @@
     super options,data

     @anchor = new ProfileLinkView origin: data.anchor
-
+
   render:->
-
+
   addCommentBox:->
-
+
   viewAppended:->
     @setTemplate @pistachio()
     @template.update()
-
+
   pistachio:->
     """

@@ -63,15 +63,15 @@

 class AccountFollowBucketItemView extends FollowBucketItemView
-
+
   constructor:(options, data)->
     options.subItemLinkClass or= ProfileLinkView
     options.subItemCssClass or= 'profile'
     super

 class TagFollowBucketItemView extends FollowBucketItemView
-
+
   constructor:(options, data)->
     options.subItemLinkClass or= TagLinkView
     options.subItemCssClass or= 'topic'
-    super+    super


Groups: some groupscontroller related stuff moved from the appcontroller

--- client/landing/Groups/AppController.coffee
+++ client/landing/Groups/AppController.coffee
@@ -41,18 +41,6 @@
     @isReady       = no
     KD.getSingleton('windowController').on "FeederListViewItemCountChanged", (count, itemClass, filterName)=>
       if @_searchValue and itemClass is @listItemClass then @setCurrentViewHeader count
-
-    @utils.defer @bound 'init'
-
-  init:->
-    mainController = KD.getSingleton 'mainController'
-    router         = KD.getSingleton 'router'
-    {entryPoint}   = KD.config
-    mainController.on 'NavigationLinkTitleClick', (pageInfo)=>
-      return unless pageInfo.path
-      if pageInfo.topLevel
-      then router.handleRoute "#{pageInfo.path}"
-      else router.handleRoute "#{pageInfo.path}", {entryPoint}

   onboardingText =
     everything : """


Remove old code

Remove old code

--- client/skr/components/LocationChooser.cjsx
+++ client/skr/components/LocationChooser.cjsx
@@ -14,7 +14,6 @@

     render: ->
         props = _.clone(@props)
-        s = SHARED_COLLECTION

         <LC.SelectField
             {...props}
@@ -22,13 +21,3 @@
             labelField='code'
             fetchWhenOpen={false}
             model={@props.model} />
-
-
-        # if props.selectField
-        #     <LC.SelectField sm=2
-        #         labelField="code"
-        #         setSelection={@selectSetCustomer}
-        #         getSelection={@selectGetSelection}
-        #         {...props}
-        #         model={@props.model}
-        #     />


Fix buidNum on AUR

Fix buidNum on AUR

--- tasks/publish.coffee
+++ tasks/publish.coffee
@@ -175,7 +175,7 @@
 # Publish AUR package
 gulp.task 'publish:aur', ['publish:bintray:aur'], (done) ->
   manifest.linux.name = manifest.name
-  manifest.linux.buildNum = process.env.TRAVIS_BUILD_NUMBER or manifest.buildNum
+  manifest.linux.buildNum = process.env.CIRCLE_BUILD_NUM or manifest.buildNum
   manifest.linux.productName = manifest.productName
   manifest.linux.description = manifest.description
   manifest.linux.homepage = manifest.homepage


clean routes to just put entry point

clean routes to just put entry point

--- config/routes.coffee
+++ config/routes.coffee
@@ -1,9 +1,2 @@
 exports.routes = (map) ->
-    map.get '/', 'notes#index'
-    map.get '/all', 'notes#all'
-    map.post '/all', 'notes#create'
-    map.get '/all/:id', 'notes#show'
-    map.put '/all/:id', 'notes#update'
-    map.del '/all/:id', 'notes#destroy'
-
-    map.get '/test/', 'tree#test'
+    map.get '/', 'templates#index'


don't require default_options

don't require default_options

--- app/server.coffee
+++ app/server.coffee
@@ -341,6 +341,7 @@
     _.filter _.keys(docs.parameter_docs), (k) -> k.indexOf(s) is 0

   args_to_params: ({args, default_options}) ->
+    default_options ?= {}
     if args.legnth == 0
       # you're doing it wrong
       {}


Removed 0.1.x tests.

Removed 0.1.x tests.

--- test/all.test.coffee
+++ test/all.test.coffee
@@ -1,83 +0,0 @@
-puts = console.log
-
-soda   = require 'soda'
-assert = require 'assert'
-
-{spawn} = require 'child_process'
-
-zappa_a = spawn 'zappa', ['-p', '4440', 'hi.coffee'], {cwd: 'examples/'}
-zappa_b = spawn 'zappa', ['-p', '4441', 'partials.coffee'], {cwd: 'examples/'}
-zappa_c = spawn 'zappa', ['-p', '4442', 'websockets.coffee'], {cwd: 'examples/'}
-
-# zappa.stdout.on 'data', (data) ->
-#   puts 'stdout: ' + data
-#
-# zappa.stderr.on 'data', (data) ->
-#   puts 'stderr: ' + data
-#
-# zappa.on 'exit', (code) ->
-#   puts 'zappa exited with code ' + code
-
-browser = soda.createClient
-  host: 'localhost'
-  port: 4444
-  url: 'http://localhost:4440'
-  browser: 'firefox'
-
-browser.on 'command', (cmd, args) ->
-  console.log ' \x1b[33m%s\x1b[0m: %s', cmd, args.join(', ')
-
-browser.session (err) ->
-  browser.open '/', (err, body) ->
-    browser.assertTextPresent 'hi', (error, body) ->
-      if error
-        console.log 'examples/hi.coffee failed to present text "hi"'
-        throw error
-      browser.testComplete (err, body) ->
-        console.log 'done'
-
-browser_b = soda.createClient
-  host: 'localhost'
-  port: 4444
-  url: 'http://localhost:4441'
-  browser: 'firefox'
-
-browser_b.on 'command', (cmd, args) ->
-  console.log ' \x1b[33m%s\x1b[0m: %s', cmd, args.join(', ')
-
-browser_b.session (error) ->
-  browser_b.open '/', (error, body) ->
-    browser_b.assertTextPresent 'coffeescript', (error, body) ->
-      throw error if error
-
-      browser_b.assertTextPresent 'ruby', (error, body) ->
-        throw error if error
-
-        browser_b.assertTextPresent 'python', (error, body) ->
-          throw error if error
-
-          browser_b.testComplete (err, body) ->
-            console.log 'done'
-
-browser_c = soda.createClient
-  host: 'localhost'
-  port: 4444
-  url: 'http://localhost:4442'
-  browser: 'firefox'
-
-browser_c.on 'command', (cmd, args) ->
-  console.log ' \x1b[33m%s\x1b[0m: %s', cmd, args.join(', ')
-
-browser_c.session (error) ->
-  browser_c.open '/', (error, body) ->
-    browser_c.type 'box', 'ping', (error, body) ->
-      browser_c.setTimeout 5000, (error, body) ->
-        browser_c.clickAndWait 'say', (err, body) ->
-          browser_c.assertTextPresent 'ping', (error, body) ->
-            throw error if error
-
-            browser_c.testComplete (err, body) ->
-              console.log 'done'
-              zappa_a.kill()
-              zappa_b.kill()
-              zappa_c.kill()


add acceptance tests for entity additions

--- services/document-updater/test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee
+++ services/document-updater/test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee
@@ -24,7 +24,7 @@
 				throw error if error?
 				setTimeout done, 200

-		it "should push the applied file renames to the project history changes api", (done) ->
+		it "should push the applied file renames to the project history api", (done) ->
 			rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
 				throw error if error?

@@ -52,7 +52,7 @@
 					throw error if error?
 					setTimeout done, 200

-			it "should push the applied doc renames to the project history changes api", (done) ->
+			it "should push the applied doc renames to the project history api", (done) ->
 				rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
 					throw error if error?

@@ -84,7 +84,7 @@
 					doc.pathname.should.equal @docUpdate.newPathname
 					done()

-			it "should push the applied doc renames to the project history changes api", (done) ->
+			it "should push the applied doc renames to the project history api", (done) ->
 				rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
 					throw error if error?

@@ -96,3 +96,54 @@
 					update.meta.ts.should.be.a('string')

 					done()
+
+	describe "adding a file", ->
+		before (done) ->
+			@project_id = DocUpdaterClient.randomId()
+			@fileUpdate =
+				id: DocUpdaterClient.randomId()
+				pathname: '/file-path'
+				url: 'filestore.example.com'
+			@fileUpdates = [ @fileUpdate ]
+			DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, (error) ->
+				throw error if error?
+				setTimeout done, 200
+
+		it "should push the file addition to the project history api", (done) ->
+			rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
+				throw error if error?
+
+				update = JSON.parse(updates[0])
+				update.file.should.equal @fileUpdate.id
+				update.pathname.should.equal '/file-path'
+				update.url.should.equal 'filestore.example.com'
+				update.meta.user_id.should.equal @user_id
+				update.meta.ts.should.be.a('string')
+
+				done()
+
+	describe "adding a doc", ->
+		before (done) ->
+			@project_id = DocUpdaterClient.randomId()
+			@docUpdate =
+				id: DocUpdaterClient.randomId()
+				pathname: '/file-path'
+				docLines: 'a\nb'
+			@docUpdates = [ @docUpdate ]
+			DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], (error) ->
+				throw error if error?
+				setTimeout done, 200
+
+		it "should push the doc addition to the project history api", (done) ->
+			rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
+				throw error if error?
+
+				update = JSON.parse(updates[0])
+				update.doc.should.equal @docUpdate.id
+				update.pathname.should.equal '/file-path'
+				update.docLines.should.equal 'a\nb'
+				update.meta.user_id.should.equal @user_id
+				update.meta.ts.should.be.a('string')
+
+				done()
+


Add catch to user group request. Remove console logs

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -323,13 +323,16 @@

   validateUserGroup: (props, context) ->
     if props.location.query?.group? and props.user?
-      apiClient.type('user_groups').get(props.location.query.group).then (group) =>
-        isUserMemberOfGroup = group.links?.users?.includes(props.user.id)
-        @setState({ validUserGroup: group and isUserMemberOfGroup })
-
-        if not isUserMemberOfGroup or not group
-          console.log('not group')
-          @clearUserGroupForClassification(props, context)
+      apiClient.type('user_groups').get(props.location.query.group)
+        .then (group) =>
+          isUserMemberOfGroup = group.links?.users?.includes(props.user.id)
+          @setState({ validUserGroup: group and isUserMemberOfGroup })
+
+          if not isUserMemberOfGroup or not group
+            @clearUserGroupForClassification(props, context)
+        .catch (error) =>
+          if error.status is 404
+            @clearUserGroupForClassification(props, context)

   clearUserGroupForClassification: (props, context) ->
     if (props.location.query?.group)
@@ -337,9 +340,8 @@
       @setState({ validUserGroup: false })

       Object.keys(query).forEach (key) =>
-        if query[key] is 'group'
+        if key is 'group'
           delete query[key]
-      console.log('clearing query', query)

       newLocation = Object.assign({}, props.location, { query })
       newLocation.search = ''


Throw out newly fetched subject when it's the one that's just been loaded

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -131,7 +131,8 @@

     # Take the next subject in the list, if there are any.
     unless upcomingSubjects.forWorkflow[workflow.id].length is 0
-      subject = Promise.resolve upcomingSubjects.forWorkflow[workflow.id].shift()
+      subjectToLoad = upcomingSubjects.forWorkflow[workflow.id].shift()
+      subject = Promise.resolve subjectToLoad

     # If there aren't any left (or there weren't any to begin with), refill the list.
     if upcomingSubjects.forWorkflow[workflow.id].length is 0
@@ -142,7 +143,8 @@
         sort: 'cellect' unless SKIP_CELLECT

       fetchSubjects = apiClient.type('subjects').get(subjectQuery).then (subjects) ->
-        upcomingSubjects.forWorkflow[workflow.id].push subjects...
+        nonLoadedSubjects = (newSubject for newSubject in subjects when newSubject isnt subjectToLoad)
+        upcomingSubjects.forWorkflow[workflow.id].push nonLoadedSubjects...

       # If we're filling this list for the first time, we won't have a subject selected, so try again.
       subject ?= fetchSubjects.then ->


Update Vega schema to support all new work

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -103,6 +103,19 @@
     target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.', default: {x: 20, y: 20}}
     duration: {title: 'Duration', description: 'Number of milliseconds over which to move, or 0 for an instant move.', type: 'integer', minimum: 0, format: 'milliseconds'}
   tutorial: c.object {title: 'Tutorial', description: 'Move Vega around on the screen as a step by step tutorial to explain things.', default: {targetElement: 'editor', animation: 'Outline'}},
+    position: c.shortString(
+      title: 'Dialog Position'
+      description: 'What side (or no side with "stationary") of the target element to move the tutorial modal to'
+      enum: [
+        'smart'
+        'stationary'
+        'left'
+        'top'
+        'right'
+        'down'
+      ]
+      default: 'smart'
+    )
     targetElement: c.shortString(
       title: 'Target Element'
       description: 'What part of the screen to point at, like the editor, code bank or play button'
@@ -112,9 +125,8 @@
         'Play Button'
         'Update Button'
         'Goal List'
-        'Code Bank button'
+        'Code Bank Button'
         'Code Editor Window'
-        'Intro / Center'
       ]
       default: 'Code Editor Window'
     )
@@ -127,6 +139,13 @@
         'Zoom'
       ]
     )
+    targetLine: {title: 'Target Line', description: 'Highlight a specific line in the code editor', type: 'number'}
+    grayOverlay: {
+      title: 'Gray Overlay'
+      description: 'Fade out the rest of the website, except the message itself and the targetElement (if you set one).'
+      type: 'boolean'
+      default: true
+    }

 NoteGroupSchema = c.object {title: 'Note Group', description: 'A group of notes that should be sent out as a result of this script triggering.', displayProperty: 'name'},
   name: {title: 'Name', description: 'Short name describing the script, like \"Anya greets the player\", for your convenience.', type: 'string'}


WIP: guests group

WIP: guests group

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -104,10 +104,7 @@

   handleRoute:(userRoute, options={})->

-    console.trace()
-    console.log
-
-    userRoute = userRoute.slice 1  if userRoute.indexOf('!') is 0
+    userRoute = userRoute.slice 1  if (userRoute.indexOf '!') is 0
     @visitedRoutes.push userRoute

     [frag, query...] = (userRoute ? @getDefaultRoute?() ? '/').split '?'


Further work on parser

Further work on parser

--- src/engine/interpreter/source-tree.coffee
+++ src/engine/interpreter/source-tree.coffee
@@ -125,7 +125,10 @@
   findExpressionNodeClass: (tokens) ->
     return ExpressionNode  # TODO!

-
+  nextToken: (tokens) ->
+    while tokens.length > 0 and (tokens[0].type == @language.tokenType.EOL or tokens[0].type == @language.tokenType.EOF)
+      tokens = tokens[1..]
+    return if tokens.length > 0 then tokens[0] else @language.tokenType.EOF

   class TreeNode
     constructor: (@parent = undefined)->
@@ -212,6 +215,10 @@
       if not @thenNode instanceof ThenNode
         return new ParseError(type = 'Syntax Error', message = 'Expected THEN')

+      # Check singleline IF THEN
+      if @thenNode.singleline and self.nextToken(tokens).type not in [self.language.tokenType.Else, self.language.tokenType.End]
+        return tokens
+
       # Build ELSE and ELSE IF
       while true

@@ -232,6 +239,10 @@
         else if node instanceof ElseNode
           @elseNode = node

+          # Check singleline IF THEN ELSE
+          if @thenNode.singleline and @elseNode.singleline and self.nextToken(tokens).type not in [self.language.tokenType.Else, self.language.tokenType.End]
+            return tokens
+
         # END node
         else if node instanceof EndNode
           return tokens
@@ -241,26 +252,64 @@
           return new ParseError(type = 'Syntax Error', message = 'Expected END')

   class ThenNode extends BlockNode
+    constructor: ->
+      super()
+      @singleline = false
+
     build: (tokens) ->

       # Consume THEN token
       @tokens.push(tokens[0])
       tokens = tokens[1..]

-      # Build statements
-      return super(tokens, endNodes = [ElseIfNode, ElseNode, EndNode])
+      # Check singleline vs multiline
+      @singleline = tokens[0].type != self.language.tokenType.EOL
+
+      # Singleline THEN
+      if @singleline
+        @singleline = true
+
+        [node, tokens] = self.buildStatement(tokens)
+        @children.push(node)
+
+        @tokens = @tokens.concat(node.tokens)
+
+        return tokens
+
+      # Multiline THEN
+      else
+        return super(tokens, endNodes = [ElseIfNode, ElseNode, EndNode])

   class ElseIfNode extends TreeNode

   class ElseNode extends BlockNode
+    constructor: ->
+      super()
+      @singleline = false
+
     build: (tokens) ->

       # Consume ELSE token
       @tokens.push(tokens[0])
       tokens = tokens[1..]

-      # Build statements
-      return super(tokens, endNodes = [ElseIfNode, ElseNode, EndNode])
+      # Check singleline vs multiline
+      @singleline = tokens[0].type != self.language.tokenType.EOL
+
+      # Singleline ELSE
+      if @singleline
+        @singleline = true
+
+        [node, tokens] = self.buildStatement(tokens)
+        @children.push(node)
+
+        @tokens = @tokens.concat(node.tokens)
+
+        return tokens
+
+      # Multiline ELSE
+      else
+        return super(tokens, endNodes = [ElseIfNode, ElseNode, EndNode])

   class EndNode extends TreeNode


 Merge branch 'master.debounceBackCompat' into 'master'

Master.Debounce Backwards compatible

--- core/utils.coffee
+++ core/utils.coffee
@@ -488,6 +488,10 @@
   ###

   throttle : (wait, func)->
+    # for backwards compatibility
+    if (typeof func) is "number"
+      [wait, func] = [func, wait]
+
     context = args = timeout = throttling = more = null
     whenDone = KD.utils.debounce wait, -> more = throttling = false
     ->
@@ -506,6 +510,9 @@
       throttling = yes

   debounce : (wait, func)->
+    if (typeof func) is "number"
+      [wait, func] = [func, wait]
+
     timeout   = null
     ->
       context = this


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/tweening.coffee
+++ test/tweening.coffee
@@ -5,6 +5,8 @@
     x: 0

   o.tween 10,
+    x: 10
+

   # Test for equality of two objects


Attrs to set now omits 'null' and 'undefined' values on render

All string interpolated attributes has value 'null'.

--- attrsToSet.coffee
+++ attrsToSet.coffee
@@ -34,7 +34,8 @@

 		# set current attributes
 		for attr of @attrs
-			clone.setAttribute attr, null
+			if node._attrs[attr]?
+				clone.setAttribute attr, null

 		# listen on changes
 		node.onAttrsChange @setAttribute, clone


Now passing the tests for file read/write

--- src/file.coffee
+++ src/file.coffee
@@ -42,15 +42,15 @@
 exports.save = saveFile = (file, data) ->
     saveFile = getFilePath file
     saveData = JSON.stringify data
+
     fs.writeFile saveFile, saveData, (err) ->
         if err
             logger.warn "There has been an error writing " + getFilePath file
             logger.log err.message
-            false
         else
             logger.success "Data saved to " + getFilePath file
-            true

+    (fs.existsSync file)

 ###
  * Read File


redirect all routes to index

redirect all routes to index

--- app/lib/router.coffee
+++ app/lib/router.coffee
@@ -5,6 +5,7 @@

   routes:
     '': 'index'
+    '*all': 'redirect'

   index: ->
     $('#content').append app.bannerView.render().el
@@ -20,3 +21,6 @@
         app.playlists.first().set selected:true
     else
       $('#content').append app.welcomeView.render().el
+
+  redirect: ->
+    @navigate ''


Replaced "loadUrl" with "loadURL"

Replaced "loadUrl" with "loadURL"

--- index.cjsx
+++ index.cjsx
@@ -43,7 +43,7 @@
   newWindow.on 'move', handleWindowMoveResize
   newWindow.on 'resize', handleWindowMoveResize
   newWindow.on 'closed', -> window.poiStatsWindow = null
-  newWindow.loadUrl "file://#{__dirname}/index.html"
+  newWindow.loadURL "file://#{__dirname}/index.html"
   newWindow.show()
   if process.env.DEBUG?
     window.log 'Poi Stats started.'


Coding style

Coding style

--- index.cjsx
+++ index.cjsx
@@ -7,12 +7,12 @@
 {__} = i18n

 i18n.configure({
-    locales:['en-US', 'ja-JP', 'zh-CN'],
-    defaultLocale: 'zh-CN',
-    directory: path.join(__dirname, "i18n"),
-    updateFiles: false,
-    indent: "\t",
-    extension: '.json'
+  locales:['en-US', 'ja-JP', 'zh-CN'],
+  defaultLocale: 'zh-CN',
+  directory: path.join(__dirname, "i18n"),
+  updateFiles: false,
+  indent: "\t",
+  extension: '.json'
 })
 i18n.setLocale(window.language)

@@ -30,36 +30,36 @@
   ), 5000)

 initialPoiStatsWindow = ->
-    defaultBounds = {x: 0, y: 0, width: 800, height: 750}
-    b = config.get 'plugin.PoiStatistics.bounds', defaultBounds
-    newWindow = windowManager.createWindow
-        x: b.x
-        y: b.y
-        width: b.width
-        height: b.height
-        realClose: true
-    window.poiStatsWindow = newWindow
-    newWindow.setMinimumSize 600, 425
-    newWindow.on 'move', handleWindowMoveResize
-    newWindow.on 'resize', handleWindowMoveResize
-    newWindow.on 'closed', -> window.poiStatsWindow = null
-    newWindow.loadUrl "file://#{__dirname}/index.html"
-    newWindow.show()
-    if process.env.DEBUG?
-        window.log 'Poi Stats started.'
-        newWindow.openDevTools
-            detach: true
+  defaultBounds = {x: 0, y: 0, width: 800, height: 750}
+  b = config.get 'plugin.PoiStatistics.bounds', defaultBounds
+  newWindow = windowManager.createWindow
+    x: b.x
+    y: b.y
+    width: b.width
+    height: b.height
+    realClose: true
+  window.poiStatsWindow = newWindow
+  newWindow.setMinimumSize 600, 425
+  newWindow.on 'move', handleWindowMoveResize
+  newWindow.on 'resize', handleWindowMoveResize
+  newWindow.on 'closed', -> window.poiStatsWindow = null
+  newWindow.loadUrl "file://#{__dirname}/index.html"
+  newWindow.show()
+  if process.env.DEBUG?
+    window.log 'Poi Stats started.'
+    newWindow.openDevTools
+      detach: true

 module.exports =
-    name: 'PoiStatistics'
-    priority: 110
-    displayName: <FontAwesome name='bar-chart' key={0} /> {__ 'Poi Statistics'}
-    author: 'Alvin Yu'
-    link: 'https://github.com/alvin-777/poi-plugin-poi-stats'
-    version: '1.1.2'
-    description: __ 'PluginDesc'
-    handleClick: ->
-        if window.poiStatsWindow != null
-            window.poiStatsWindow.show()
-        else
-            initialPoiStatsWindow()
+  name: 'PoiStatistics'
+  priority: 110
+  displayName: <FontAwesome name='bar-chart' key={0} /> {__ 'Poi Statistics'}
+  author: 'Alvin Yu'
+  link: 'https://github.com/alvin-777/poi-plugin-poi-stats'
+  version: '1.1.2'
+  description: __ 'PluginDesc'
+  handleClick: ->
+    if window.poiStatsWindow != null
+      window.poiStatsWindow.show()
+    else
+      initialPoiStatsWindow()


Only translate valid task id

Only translate valid task id

--- index.cjsx
+++ index.cjsx
@@ -39,7 +39,7 @@
       }
     handleTaskChange: (e) ->
       {tasks} = e.detail
-      for task in tasks when task.id isnt 100000
+      for task in tasks when task.id < 100000
         if @state.quests[task.id]?
           quest = @state.quests[task.id]
           task.content =
{categoryNames[quest.category]} - {typeNames[quest.type]}
{quest.condition}