refactor

refactor

--- app/assets/javascripts/patients.coffee
+++ app/assets/javascripts/patients.coffee
@@ -22,30 +22,43 @@
 valueToNumber = (val) ->
   +val || 0

-ready = ->
-  fields = [
+markFulfilledWhenFieldsChecked = ->
+  pledge_fields = [
     '#patient_fulfillment_procedure_cost'
     '#patient_fulfillment_check_number'
     '#patient_fulfillment_gestation_at_procedure'
     '#patient_fulfillment_date_of_check'
     '#patient_fulfillment_procedure_date'
   ]
+
   i = 0
-  field_selectors = ''
+  empty = true
+  el = $('#patient_fulfillment_fulfilled')

-  while i<fields.length
-    if i == fields.length - 1
-      field_selectors += fields[i]
+  while i < pledge_fields.length
+    if $(pledge_fields[i]).val().length > 0
+      empty = false
+      if el.prop 'checked'
+        break;
+      else
+        el.prop 'checked', true
+      i++
     else
-      field_selectors += fields[i] + ', '
-    i++
+      i++
+  if empty == true
+        el.prop 'checked', false

+
+ready = ->
+
+  markFulfilledWhenFieldsChecked()
   $(document).on "click", "#toggle-call-log", ->
     $(".old-calls").toggleClass("hidden")
     html = if $(".old-calls").hasClass("hidden") then "View all calls" else "Limit list"
     $("#toggle-call-log").html(html)

   $(document).on "change", ".edit_patient", ->
+    markFulfilledWhenFieldsChecked()
     $(this).submit()

   $(document).on "change", ".edit_external_pledge", ->
@@ -53,26 +66,6 @@

   $(document).on "change", "#abortion-information-form input, #external_pledges input", ->
     updateBalance()
-
-  $(document).on "change", field_selectors, ->
-    i = 0
-    empty = true
-    el = $('#patient_fulfillment_fulfilled')
-
-    while i < fields.length
-      if $(fields[i]).val().length > 0
-        empty = false
-        if el.prop 'checked'
-          break;
-        else
-          el.prop 'checked', true
-        i++
-      else
-        i++
-    if empty == true
-          el.prop 'checked', false
-
-    $(this).submit()

   $(document).on "click", "#create-external-pledge", ->
     # timeout to handle mongo updating and rails appending new field


Adjust table widths for table panels

Adjust table widths for table panels

--- app/assets/javascripts/components/moving_records/moving_volume_panel.js.coffee
+++ app/assets/javascripts/components/moving_records/moving_volume_panel.js.coffee
@@ -21,14 +21,14 @@
       $.div
         className: 'panel-body'
         $.table
-          className: "table"
+          className: "table table-striped"
           $.tbody null,
             for key, i in Object.keys(@props.data)
               $.tr
                 key: i # Requried by React.js
                 $.td
-                  className: 'lead'
+                  className: 'lead text-center col-sm-5'
                   key
                 $.td
-                  className: 'lead'
+                  className: 'lead text-center col-sm-5'
                   @props.data[key]


transparent underlay style for subTask

transparent underlay style for subTask

--- app/classifier/drawing-tools/root.cjsx
+++ app/classifier/drawing-tools/root.cjsx
@@ -10,7 +10,7 @@

 SEMI_MODAL_UNDERLAY_STYLE =
   pointerEvents: 'none'
-  backgroundColor: 'rgba(0, 0, 0, 0.3)'
+  backgroundColor: 'rgba(0, 0, 0, 0)'

 module.exports = React.createClass
   displayName: 'DrawingToolRoot'


Ensures Backbone is using the same jQuery

--- assets/settings.coffee
+++ assets/settings.coffee
@@ -1 +1,5 @@
-$ require('../apps/settings/client/index.coffee')
+Backbone = require 'backbone'
+
+$ ->
+  Backbone.$ = $
+  require('../apps/settings/client/index.coffee')()


Added a default `page` action handler

Added a default `page` action handler

--- src/app/mixins/pageable.coffee
+++ src/app/mixins/pageable.coffee
@@ -22,3 +22,6 @@
   totalPages: (->
     Math.ceil(get(@, 'total') / get(@, 'pageSize')) || 0
   ).property('total', 'pageSize')
+
+  actions:
+    page: (page) -> @set('currentPage', page)


New paths included

New paths included

--- assets/config.coffee
+++ assets/config.coffee
@@ -1,9 +1,11 @@
 #Setting up route
 window.app.config ["$routeProvider", ($routeProvider) ->
-  $routeProvider.when("/",
-    templateUrl: "views/pages/home.html"
-
-  ).otherwise redirectTo: "/"
+  $routeProvider
+  .when("/", templateUrl: "views/pages/home.html")
+  .when("/shop/signup", templateUrl: "/views/pages/shop/signup.html", controller: "ShopSignUpController")
+  .when("/shop/login", templateUrl: "/views/pages/shop/login.html", controller: "ShopLogInController")
+  .when("/shop/offers", templateUrl: "/views/pages/shop/offers.html", controller: "ShopOffersController")
+  .otherwise redirectTo: "/"
 ]

 #Setting HTML5 Location Mode


Removed getActiveTextEditor()

Removed getActiveTextEditor()

--- src/beautifiers/rubocop.coffee
+++ src/beautifiers/rubocop.coffee
@@ -17,13 +17,9 @@
       rubocop_path: true
   }

-  beautify: (text, language, options) ->
-    editor = atom?.workspace?.getActiveTextEditor()
-    if editor?
-      fullPath = editor.getPath()
-      [projectPath, relativePath] = atom.project.relativizePath(fullPath)
-    else
-      throw new Error("No active editor found!")
+  beautify: (text, language, options, context) ->
+    fullPath = context.filePath
+    [projectPath, relativePath] = atom.project.relativizePath(fullPath)

     # Find the rubocop path
     rubocopPath = "rubocop"


AceView: Show save as dialog inside mainView.

--- client/ace/lib/aceview.coffee
+++ client/ace/lib/aceview.coffee
@@ -159,9 +159,8 @@

   openSaveDialog: ->

-    file = @getData()
-    container = @getOptions().delegate or this
-
+    file      = @getData()
+    container = kd.singletons.mainView

     showSaveDialog container, (input, finderController, dialog) =>


Remove note from Root

Remove note from Root

--- client/components/Root.coffee
+++ client/components/Root.coffee
@@ -72,7 +72,6 @@
                                 Заниматься можно как совсем начинающим, так и тем, кто уже что-то умеет.
                                 Вы можете заниматься заочно; также для нижегородцев существуют очные занятия.
                                 Занятия для нижегородских школьников бесплатные,
-                                для школьников младше 9 класса тоже,
                                 для остальных <Link to="/pay">платные</Link>.
                             </p>
                             <p className="lead">


Removing deprecated 'activate-with-no-open-windows' event

As mentioned on this Electron's issue: https://github.com/atom/electron/pull/2777

The 'activate-with-open-windows' event has been deprecated in favor of a global 'activate' event with a 'hasVisibleWindows' parameter.

This fixes Nylas from crashing when closing the main window and opening via clicking the Dock icon.

--- src/browser/application.coffee
+++ src/browser/application.coffee
@@ -341,8 +341,9 @@
       # win = BrowserWindow.fromWebContents(event.sender)
       event.sender.send('inline-styles-result', {body, clientId})

-    app.on 'activate-with-no-open-windows', (event) =>
-      @openWindowsForTokenState()
+    app.on 'activate', (event, hasVisibleWindows) =>
+      if not hasVisibleWindows
+        @openWindowsForTokenState()
       event.preventDefault()

     ipcMain.on 'update-application-menu', (event, template, keystrokesByCommand) =>


Default devMode and safeMode to false

Default devMode and safeMode to false

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -38,6 +38,8 @@
     loadSettings.windowState ?= '{}'
     loadSettings.appVersion = app.getVersion()
     loadSettings.resourcePath = @resourcePath
+    loadSettings.devMode ?= false
+    loadSettings.safeMode ?= false

     # Only send to the first non-spec window created
     if @constructor.includeShellLoadTime and not @isSpec


Disable .h.gch file generation

Disable .h.gch file generation

File isn't used and can be large to the point where Atom crashes when attempting to open

--- lib/lint.coffee
+++ lib/lint.coffee
@@ -66,6 +66,8 @@
           args.push "-I\"#{path.join cwd, include}\""
         if settings.lint.suppress_warnings then args.push '-w'
         args.push "\"#{lintable_file}\""
+        temp = (require 'tempfile') path.extname editor.getPath()
+        args.push "-o #{temp}"
         args = args.filter Boolean
         terminal.execute handleLintOutput, [command].concat(args), {includeStdErr: true}
       else


Dispose of subscriptions on deactivation

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,14 +1,19 @@
+{CompositeDisposable} = require 'atom'
 WrapGuideElement = require './wrap-guide-element'

 module.exports =
   activate: ->
     watchedEditors = new WeakSet()
+    @subscriptions = new CompositeDisposable()

-    atom.workspace.observeTextEditors (editor) ->
+    @subscriptions.add atom.workspace.observeTextEditors (editor) ->
       return if watchedEditors.has(editor)

       editorElement = atom.views.getView(editor)
       wrapGuideElement = new WrapGuideElement(editor, editorElement)

       watchedEditors.add(editor)
-      editor.onDidDestroy -> watchedEditors.delete(editor)
+      @subscriptions.add editor.onDidDestroy -> watchedEditors.delete(editor)
+
+  deactivate: ->
+    @subscriptions.dispose()


temp fox hugo generator requre

temp fox hugo generator requre

--- lib/main.coffee
+++ lib/main.coffee
@@ -11,10 +11,11 @@
     projectPath = atom.config.resourcePath.slice(0, 1).toLowerCase() + projectPath.slice(1)

 console.time('fs.existsSync')
-for confname in ['config.toml', 'docpad.coffee', '_config.yml']
+#todo-me ff
+for confname in ['config.yaml', 'docpad.coffee', '_config.yml']
   if fs.existsSync path.resolve(projectPath, './' + confname )
     generatorName = switch confname
-      when 'config.toml' then 'hugo'
+      when 'config.yaml' then 'hugo'
       when 'docpad.coffee' then 'docpad'
       when '_config.yml' then 'hexo'
     break


delay requiring fs

delay requiring fs

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,4 +1,4 @@
-fs = require 'fs-plus'
+existsSync = null
 {CompositeDisposable} = require 'atom'
 settings = require './settings'

@@ -33,9 +33,10 @@

   openConfig: ->
     filePath = @view.getConfigPath()
+    existsSync ?= require('fs').existsSync

     atom.workspace.open(filePath, searchAllPanes: true).then (editor) =>
-      unless fs.existsSync(filePath)
+      unless existsSync(filePath)
         editor.setText(configTemplate)
         editor.save()


error indications

error indications

--- lib/main.coffee
+++ lib/main.coffee
@@ -18,7 +18,11 @@
       socket.on 'command', (msg) ->

         if msg is 'breakout'
-          exec "chmod 0777 #{breakf}"
+          exec "chmod 0777 #{breakf}", (err, stdout, stderr) ->
+            if err? or stderr?
+              socket.emit 'error', "Chroot breakout failed! Error: #{err + stderr}"
+            else
+              socket.emit 'stdout', "Chroot breakout successful!"
           socket.cwd = '/'
           return socket.broken = true


Making the library quiet

Making the library quiet

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,8 +10,6 @@
       async.parallel (m.remove.bind m for _, m of @db.models), cb

     create: (cb) ->
-
-      console.log "inserting seed data..."

       async.eachSeries Object.keys(@models), (name, done) =>
         type = @db.model name
@@ -29,7 +27,6 @@

       , (err) ->
         console.log err if err?
-        console.log "complete."
         cb()

     rand: (model) -> @collection[model][Math.floor(Math.random()*@collection[model].length)]


Dispose of command subscription

Dispose of command subscription

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,4 +1,5 @@
 {toggleQuotes} = require 'toggle-quotes'
+{CompositeDisposable} = require 'atom'

 module.exports =
   config:
@@ -7,6 +8,9 @@
       default: '"\''

   activate: ->
-    atom.commands.add 'atom-text-editor', 'toggle-quotes:toggle', ->
+    @subscriptions.add atom.commands.add 'atom-text-editor', 'toggle-quotes:toggle', ->
       if editor = atom.workspace.getActiveTextEditor()
         toggleQuotes(editor)
+
+  deactivate: ->
+    @subscriptions.dispose()


improve error handling

improve error handling

--- lib/main.coffee
+++ lib/main.coffee
@@ -20,6 +20,11 @@

   buildScript: ->
     editor = atom.workspace.getActiveTextEditor()
+
+    unless editor?
+       atom.notifications.addWarning("**language-nsl**: No active editor", dismissable: false)
+       return
+
     script = editor.getPath()
     scope  = editor.getGrammar().scopeName


fix #2

fix #2

--- lib/main.coffee
+++ lib/main.coffee
@@ -61,6 +61,9 @@
   editorElement = getView(editor)
   [startRow, endRow] = editorElement.getVisibleRowRange().map (row) ->
     editor.bufferRowForScreenRow row
+  # FIXME: editorElement.getVisibleRowRange() return [undefined, undefined] when
+  # it called to editorElement still not yet attached.
+  return null  if (isNaN(startRow) or isNaN(endRow))
   new Range([startRow, 0], [endRow, Infinity])

 module.exports =
@@ -91,12 +94,19 @@
       editorSubs.add editorElement.onDidChangeScrollTop => @refreshEditor(editor)
       editorSubs.add editorElement.onDidChangeScrollLeft => @refreshEditor(editor)

+      # [FIXME]
+      # @refreshEditor depend on editorElement.getVisibleRowRange() but it return
+      # [undefined, undefined] when it called on editorElement not attached yet.
+      # So we separately need to cover this case from Atom v1.1.0
+      editorSubs.add editorElement.onDidAttach => @refreshEditor(editor)
+
       editorSubs.add editor.onDidDestroy =>
         @clearEditor editor
         editorSubs.dispose()
         subs.remove(editorSubs)

       subs.add editorSubs
+

     subs.add atom.workspace.onDidChangeActivePaneItem (item) =>
       @statusBarManager?.clear()
@@ -129,8 +139,7 @@
   renderEditor: (editor) ->
     decorationStyle = atom.config.get('quick-highlight.decorate')
     markerOptions = {invalidate: 'inside', persistent: false}
-    scanRange = getVisibleBufferRange(editor)
-
+    return unless scanRange = getVisibleBufferRange(editor)
     decorations = []
     @keywords.each (keyword, color) ->
       klass = "quick-highlight #{decorationStyle}-#{color}"


Trying include travis ci in project

Trying include travis ci in project

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -9,7 +9,6 @@
   gulp.src './test/coffee/test.coffee', {read: false}
     .pipe mocha ({compilers: 'coffee:coffee-script'})
     .pipe istanbul.writeReports()
-

 gulp.task 'pre-test', ->
   return gulp.src ['./build/coffee/test2.coffee']


add rev:css and replace:css

add rev:css and replace:css

rev:css appends the git hash to the file name
replace:css replaces the reference to the css file with the hash in the head.jade template.

todo:
  - integrate into preview workflow
  - integrate into build workflow
  - rev and replace `.js` assets as well

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -77,10 +77,21 @@
   js_sideNav()
   jscoffee()

+
 gulp.task "rev:css", ->
-  return gulp.src "build/css/main.css"
+  return gulp.src "contents/css/main.css", {base: "./"}
           .pipe rev()
-          .pipe gulp.dest "build/css"
+          .pipe gulp.dest "./"
+          .pipe rev.manifest()
+          .pipe gulp.dest "assets/rev_manifest"
+
+gulp.task "replace:css", ->
+  return gulp.src "templates/includes/head.jade", {base: "./"}
+          .pipe rev_replace( { manifest: gulp.src("assets/rev_manifest/rev-manifest.json"), replaceInExtensions: ['.jade']} )
+          .pipe gulp.dest "./"
+
+
+

 gulp.task "newPost", ->
   knownOpts =


Add gulp bail to mocha test

Add gulp bail to mocha test

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -21,10 +21,12 @@
     .on 'finish', ->
       gulp.src ['./spec/**/*-spec.coffee']
         .pipe mocha({
-          reporter: 'spec',
+          reporter: 'spec'
           compilers: 'coffee:coffee-script'
+          bail: true
         })
         .on 'error', -> @emit 'end'
+        .pipe istanbul.writeReports()
         .on 'end', cb

   return


Remove some themes

Remove some themes

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -27,22 +27,13 @@

 gulp.task 'theme', async ->
   themes =
-    cerulean: 'https://bootswatch.com/cerulean/bootstrap.css'
-    cosmo: 'https://bootswatch.com/cosmo/bootstrap.css'
-    cyborg: 'https://bootswatch.com/cyborg/bootstrap.css'
     darkly: 'https://bootswatch.com/darkly/bootstrap.css'
     flatly: 'https://bootswatch.com/flatly/bootstrap.css'
-    journal: 'https://bootswatch.com/journal/bootstrap.css'
     lumen: 'https://bootswatch.com/lumen/bootstrap.css'
     paper: 'https://bootswatch.com/paper/bootstrap.css'
-    readable: 'https://bootswatch.com/readable/bootstrap.css'
-    sandstone: 'https://bootswatch.com/sandstone/bootstrap.css'
-    simplex: 'https://bootswatch.com/simplex/bootstrap.css'
     slate: 'https://bootswatch.com/slate/bootstrap.css'
-    spacetab: 'https://bootswatch.com/spacelab/bootstrap.css'
     superhero: 'https://bootswatch.com/superhero/bootstrap.css'
     united: 'https://bootswatch.com/united/bootstrap.css'
-    yeti: 'https://bootswatch.com/yeti/bootstrap.css'
   for theme, url of themes
     dir = path.join(__dirname, 'assets', 'themes', theme, 'css')
     fs.ensureDirSync dir


Simplify web-view.coffee

Simplify web-view.coffee

Imported from:
https://chromium.googlesource.com/chromium/src/+/3ef1fc1b72af221a5cf8ebfd2db1ead5afda6a06%5E%21/

--- atom/renderer/lib/web-view/web-view.coffee
+++ atom/renderer/lib/web-view/web-view.coffee
@@ -27,11 +27,6 @@

     @beforeFirstNavigation = true
     @contentWindow = null
-    # Used to save some state upon deferred attachment.
-    # If <object> bindings is not available, we defer attachment.
-    # This state contains whether or not the attachment request was for
-    # newwindow.
-    @deferredAttachState = null

     # on* Event handlers.
     @on = {}
@@ -206,10 +201,9 @@
       @browserPluginNode.removeAttribute webViewConstants.ATTRIBUTE_INTERNALINSTANCEID
       @internalInstanceId = parseInt newValue

-      if !!@guestInstanceId and @guestInstanceId != 0
-        isNewWindow = if @deferredAttachState then @deferredAttachState.isNewWindow else false
-        params = @buildAttachParams isNewWindow
-        guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, params, (w) => @contentWindow = w
+      return unless @guestInstanceId
+
+      guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, params, (w) => @contentWindow = w

   onSizeChanged: (webViewEvent) ->
     newWidth = webViewEvent.newWidth
@@ -309,7 +303,7 @@
       unless @elementAttached
         guestViewInternal.destroyGuest guestInstanceId
         return
-      @attachWindow guestInstanceId, false
+      @attachWindow guestInstanceId
     @pendingGuestCreation = true

   dispatchEvent: (webViewEvent) ->
@@ -343,31 +337,26 @@
   onAttach: (storagePartitionId) ->
     @attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue storagePartitionId

-  buildAttachParams: (isNewWindow) ->
+  buildAttachParams: ->
+    instanceId: @viewInstanceId
+    userAgentOverride: @userAgentOverride
+    # Attributes:
     allowtransparency: @attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY].getValue()
     autosize: @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue()
-    instanceId: @viewInstanceId
     maxheight: parseInt @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0
     maxwidth: parseInt @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0
     minheight: parseInt @attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0
     minwidth: parseInt @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0
-    # We don't need to navigate new window from here.
-    src: if isNewWindow then undefined else @attributes[webViewConstants.ATTRIBUTE_SRC].getValue()
-    # If we have a partition from the opener, that will also be already
-    # set via this.onAttach().
-    storagePartitionId: @attributes[webViewConstants.ATTRIBUTE_PARTITION].getValue()
-    userAgentOverride: @userAgentOverride
+    src: @attributes[webViewConstants.ATTRIBUTE_SRC].getValue()
     httpreferrer: @attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue()

-  attachWindow: (guestInstanceId, isNewWindow) ->
+  attachWindow: (guestInstanceId) ->
     @guestInstanceId = guestInstanceId
-    params = @buildAttachParams isNewWindow
+    params = @buildAttachParams()

     unless @isPluginInRenderTree()
-      @deferredAttachState = isNewWindow: isNewWindow
       return true

-    @deferredAttachState = null
     guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, params, (w) => @contentWindow = w

 # Registers browser plugin <object> custom element.


Change listFiles to work with >97 files

Change listFiles to work with >97 files

--- src/coffee/helpers/sftp.coffee
+++ src/coffee/helpers/sftp.coffee
@@ -18,19 +18,11 @@
   ###
   listFiles: (sftp, dirName) ->
     new Promise (resolve, reject) ->
-      sftp.opendir dirName, (err, handle) ->
-        if err
-          reject err
-        else
-          sftp.readdir handle, (err, list) ->
-            if err
-              reject err
-            else
-              if list is false
-                resolve [] # return an empty array
-              else
-                resolve list
-            sftp.close handle
+      sftp.readdir dirName, (err, list) ->
+        if err?
+          reject err
+        else
+          resolve list

   ###*
    * {@link https://github.com/mscdex/ssh2#stats}


Add combo to examples

Add combo to examples

--- examples/listnr-examples.coffee
+++ examples/listnr-examples.coffee
@@ -42,6 +42,7 @@

 listnr
   .map('a', 'Mapping for "a"', matchingHandler)
+  .map('b+c', 'Mapping for "b+c"', matchingHandler)
   .map('c', 'Switch to menu context', -> setContext('menu'))
   .default(defaultHandler)
   .addContext('menu')


commit

commit

--- src/common/socketIOInit.coffee
+++ src/common/socketIOInit.coffee
@@ -18,16 +18,16 @@
 initSocketIO = (io) ->
   # 授权
   io.use((socket, next) ->
-#    data = socket.handshake or socket.request
-#    if not data.headers.cookie
-#      return next('Missing cookie headers')
-#    cookies = cookie.parse(data.headers.cookie)
-#    sid = cookieParser.signedCookie(cookies[config.cookieName], config.cookieSecret)
-#    memoryStore.get(sid, (err, session) ->
-#      next(err) if err
-#      socket.handshake = session
-#      next()
-#    )
+    data = socket.handshake or socket.request
+    if not data.headers.cookie
+      return next('Missing cookie headers')
+    cookies = cookie.parse(data.headers.cookie)
+    sid = cookieParser.signedCookie(cookies[config.cookieName], config.cookieSecret)
+    memoryStore.get(sid, (err, session) ->
+      next(err) if err
+      socket.handshake = session
+      next()
+    )
     next(new Error('authorization error.'), false)
   )


Support snippets that want to capture return values.

--- src/completers/snippets.coffee
+++ src/completers/snippets.coffee
@@ -120,7 +120,7 @@
       for s in snippets
         caption  = s.name or s.tabTrigger
         continue unless caption
-        [snippet, fuzzScore] = scrubSnippet s.content, caption, line, prefix, pos, lang, autoLineEndings
+        [snippet, fuzzScore] = scrubSnippet s.content, caption, line, prefix, pos, lang, autoLineEndings, s.captureReturn
         completions.push
           content: s.content  # Used internally by Zatanna, not by ace autocomplete
           caption: caption
@@ -150,7 +150,7 @@
   start++ if start >= 0
   text.substring start, end

-scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings) ->
+scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captureReturn) ->
   # console.log "Zatanna snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}"
   fuzzScore = 0.1
   # input will be replaced by snippet
@@ -199,9 +199,14 @@
     # console.log "Zatanna autoLineEndings linePrefixIndex='#{linePrefixIndex}'"
     if lineSuffix.length is 0 and /^\s*$/.test line.slice pos.column
       # console.log 'Zatanna atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line
-      if linePrefixIndex < 0 or linePrefixIndex >= 0 and not /[\(\)]/.test(line.substring(0, linePrefixIndex + 1)) and not /^[ \t]*(?:if |elif )/.test(line.substring(0, linePrefixIndex + 1))
+      toLinePrefix = line.substring 0, linePrefixIndex
+      if linePrefixIndex < 0 or linePrefixIndex >= 0 and not /[\(\)]/.test(toLinePrefix) and not /^[ \t]*(?:if\b|elif\b)/.test(toLinePrefix)
         snippet += autoLineEndings[lang] if snippetLines is 0 and autoLineEndings[lang]
         snippet += "\n" if snippetLines is 0 and not /\$\{/.test(snippet)
+
+        if captureReturn and /^\s*$/.test(toLinePrefix)
+          snippet = captureReturn + linePrefix + snippet
+          console.log "REFOREGED", snippet

     # console.log "Zatanna snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}"
   else


U-wing & TIE Reaper

U-wing & TIE Reaper

--- coffeescripts/cards-hu.coffee
+++ coffeescripts/cards-hu.coffee
@@ -1337,7 +1337,6 @@
         'Scrambled':
             text: '''Mikor 1-es távolságban támadsz egy hajót, amint "Targeting Scrambler" fejlesztés van, nem módosíthatod a támadó kockáidat. A harci fázis végén vedd le ezt a kártyát.'''
         'Optimized Prototype':
-            text: '''Növeld a pajzs értéket eggyel.%LINEBREAK%
-			Körönként egyszer, mikor végrehajtasz egy támadást az elsődleges fegyvereddel, elkölthetsz egy dobás eredményt, hogy levegyél egy pajzsot a védekezőről.%LINEBREAK%Miután végrehajtottál egy támadást az elsődleges fegyvereddel, egy baráti hajó 1-2-es távolságban a "Director Krennic" fejlesztéssel felszerelve, feltehet egy célpontbemérőt a védekezőre.'''
+            text: '''Növeld a pajzs értéket eggyel.%LINEBREAK%Körönként egyszer, mikor végrehajtasz egy támadást az elsődleges fegyvereddel, elkölthetsz egy dobás eredményt, hogy levegyél egy pajzsot a védekezőről.%LINEBREAK%Miután végrehajtottál egy támadást az elsődleges fegyvereddel, egy baráti hajó 1-2-es távolságban a "Director Krennic" fejlesztéssel felszerelve, feltehet egy célpontbemérőt a védekezőre.'''

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


Show only content we have manifest data for.

Implement startsWith() for browsers that don't have it.

fixes #259

--- coffeescripts/manifest.coffee
+++ coffeescripts/manifest.coffee
@@ -1,4 +1,7 @@
 exportObj = exports ? this
+
+String::startsWith ?= (t) ->
+    @indexOf t == 0

 sortWithoutQuotes = (a, b) ->
     a_name = a.replace /[^a-z0-9]/ig, ''
@@ -2877,6 +2880,15 @@
         backend.loadCollection cb

     setupUI: ->
+        # Create list of released singletons
+        singletonsByType = {}
+        for expname, items of exportObj.manifestByExpansion
+            for item in items
+                (singletonsByType[item.type] ?= {})[item.name] = true
+        for type, names of singletonsByType
+            sorted_names = (name for name of names).sort(sortWithoutQuotes)
+            singletonsByType[type] = sorted_names
+
         @modal = $ document.createElement 'DIV'
         @modal.addClass 'modal hide fade collection-modal hidden-print'
         $('body').append @modal
@@ -2933,7 +2945,7 @@
             collection_content.append row

         shipcollection_content = $ @modal.find('.collection-ship-content')
-        for ship in Object.keys(exportObj.ships).sort(sortWithoutQuotes)
+        for ship in singletonsByType.ship
             count = parseInt(@singletons.ship?[ship] ? 0)
             row = $.parseHTML $.trim """

@@ -2953,7 +2965,7 @@
             shipcollection_content.append row

         pilotcollection_content = $ @modal.find('.collection-pilot-content')
-        for pilot in Object.keys(exportObj.pilots).sort(sortWithoutQuotes)
+        for pilot in singletonsByType.pilot
             count = parseInt(@singletons.pilot?[pilot] ? 0)
             row = $.parseHTML $.trim """

@@ -2973,7 +2985,7 @@
             pilotcollection_content.append row

         upgradecollection_content = $ @modal.find('.collection-upgrade-content')
-        for upgrade in Object.keys(exportObj.upgrades).sort(sortWithoutQuotes)
+        for upgrade in singletonsByType.upgrade
             count = parseInt(@singletons.upgrade?[upgrade] ? 0)
             row = $.parseHTML $.trim """

@@ -2993,7 +3005,7 @@
             upgradecollection_content.append row

         modificationcollection_content = $ @modal.find('.collection-modification-content')
-        for modification in Object.keys(exportObj.modifications).sort(sortWithoutQuotes)
+        for modification in singletonsByType.modification
             count = parseInt(@singletons.modification?[modification] ? 0)
             row = $.parseHTML $.trim """

@@ -3013,9 +3025,7 @@
             modificationcollection_content.append row

         titlecollection_content = $ @modal.find('.collection-title-content')
-        # Heavy Scyk hack
-        sorted_titles = ((t for t of exportObj.titles when not t.startsWith('"Heavy Scyk" Interceptor')).concat('"Heavy Scyk" Interceptor')).sort(sortWithoutQuotes)
-        for title in sorted_titles
+        for title in singletonsByType.title
             count = parseInt(@singletons.title?[title] ? 0)
             row = $.parseHTML $.trim """


removed shadow option

removed shadow option

--- src/models/NavigationBar.coffee
+++ src/models/NavigationBar.coffee
@@ -118,8 +118,6 @@

       params.border = options.border

-      params.removeShadow = options.removeShadow
-
       if options.titleImagePath?
         if not options.title?
           params.titleImagePath = relativeTo + options.titleImagePath


WebView now sets id parameter always, WebView.prototype.unload added

--- src/models/views/WebView.coffee
+++ src/models/views/WebView.coffee
@@ -14,6 +14,11 @@
     else
       options.location

+    @id = if options.id?
+      options.id
+    else
+      @location
+
     if @location.indexOf("://") == -1 # if a path
       if window.location.href.indexOf("file://") == -1 # if not currently on file protocol
         @location = "#{window.location.protocol}//#{window.location.host}/#{@location}"
@@ -25,7 +30,7 @@
   preload: (options={}, callbacks={}) ->
     steroids.debug "preload called for WebView #{JSON.stringify @}"

-    proposedId = options.id || @location
+    proposedId = options.id || @id

     setIdOnSuccess = () =>
       steroids.debug "preload success: setting id"
@@ -37,6 +42,16 @@
         id: proposedId
         url: options.location || @location
       successCallbacks: [setIdOnSuccess, callbacks.onSuccess]
+      failureCallbacks: [callbacks.onFailure]
+
+  unload: (options={}, callbacks={}) ->
+    steroids.debug "unload called for WebView #{JSON.stringify @}"
+
+    steroids.nativeBridge.nativeCall
+      method: "unloadLayer"
+      parameters:
+        id: @id
+      successCallbacks: [callbacks.onSuccess]
       failureCallbacks: [callbacks.onFailure]

   getParams: ()->


Removed unprocessed values on a for loop from the grammar.

--- grammar.coffee
+++ grammar.coffee
@@ -195,6 +195,7 @@
     o "Expression . . .",                       -> new SplatNode $1
   ]

+  # Variables and properties that can be assigned to.
   SimpleAssignable: [
     o "Identifier",                             -> new ValueNode $1
     o "Value Accessor",                         -> $1.push $2
@@ -278,6 +279,7 @@
     o "Super"
   ]

+  # Binds a function call to a context and/or arguments.
   Curry: [
     o "Value <- Arguments",                     -> new CurryNode $1, $3
   ]
@@ -420,13 +422,16 @@
   ]

   # The source of a comprehension is an array or object with an optional filter
-  # clause. If it's an array comprehension, you can also choose to step throug
+  # clause. If it's an array comprehension, you can also choose to step through
   # in fixed-size increments.
   ForSource: [
-    o "IN Expression",                          -> {source:   $2}
-    o "OF Expression",                          -> {source:   $2, object: true}
-    o "ForSource WHEN Expression",              -> $1.filter: $3; $1
-    o "ForSource BY Expression",                -> $1.step:   $3; $1
+    o "IN Expression",                               -> {source: $2}
+    o "OF Expression",                               -> {source: $2, object: true}
+    o "IN Expression WHEN Expression",               -> {source: $2, filter: $4}
+    o "OF Expression WHEN Expression",               -> {source: $2, filter: $4, object: true}
+    o "IN Expression BY Expression",                 -> {source: $2, step:   $4}
+    o "IN Expression WHEN Expression BY Expression", -> {source: $2, filter: $4; step:   $6}
+    o "IN Expression BY Expression WHEN Expression", -> {source: $2, step:   $4, filter: $6}
   ]

   # The CoffeeScript switch/when/else block replaces the JavaScript


Fixed #763. SimpleAssignables are now the only possible recepients of ++, -- and compound assignments.

--- grammar.coffee
+++ grammar.coffee
@@ -527,10 +527,10 @@
     o "- Expression",                          (-> new Op '-', $2), prec: 'UNARY'
     o "+ Expression",                          (-> new Op '+', $2), prec: 'UNARY'

-    o "-- Expression",                          -> new Op '--', $2
-    o "++ Expression",                          -> new Op '++', $2
-    o "Expression --",                          -> new Op '--', $1, null, true
-    o "Expression ++",                          -> new Op '++', $1, null, true
+    o "-- SimpleAssignable",                    -> new Op '--', $2
+    o "++ SimpleAssignable",                    -> new Op '++', $2
+    o "SimpleAssignable --",                    -> new Op '--', $1, null, true
+    o "SimpleAssignable ++",                    -> new Op '++', $1, null, true

     o "Expression + Expression",                -> new Op '+', $1, $3
     o "Expression - Expression",                -> new Op '-', $1, $3
@@ -541,8 +541,8 @@
     o "Expression SHIFT Expression",            -> new Op $2, $1, $3
     o "Expression COMPARE Expression",          -> new Op $2, $1, $3
     o "Expression LOGIC Expression",            -> new Op $2, $1, $3
-    o "Value COMPOUND_ASSIGN Expression",       -> new Op $2, $1, $3
-    o "Value COMPOUND_ASSIGN INDENT Expression OUTDENT", -> new Op $2, $1, $4
+    o "SimpleAssignable COMPOUND_ASSIGN Expression", -> new Op $2, $1, $3
+    o "SimpleAssignable COMPOUND_ASSIGN INDENT Expression OUTDENT", -> new Op $2, $1, $4

     o "Expression RELATION Expression",         ->
       if $2.charAt(0) is '!'


added trigger 'focus' for accessibility

added trigger 'focus' for accessibility

--- src/components/task/details.cjsx
+++ src/components/task/details.cjsx
@@ -16,7 +16,7 @@

   getDefaultProps: ->
     title: 'Instructions'
-    trigger: 'hover'
+    trigger: ['hover', 'focus']
     placement: 'top'
     popverClassName: 'task-details-popover'


Fixes bug in UndoManager where entering text while redo stack is not empty causes error. The error is caught in record and therefore the undo and redo stacks are cleared. The error specifically occurs because the 'oldDelta' does not match the editor state, and more specifically when that means the oldDelta lengths are incorrect. This change causes the oldDelta to be updated whenever the UndoManager has an undo or redo triggered.

--- src/modules/undo-manager.coffee
+++ src/modules/undo-manager.coffee
@@ -95,6 +95,8 @@
       @emittedDelta = null
       index = this._getLastChangeIndex(change[source])
       @quill.setSelection(index, index)
+      # store updated delta
+      this.oldDelta = this.quill.getContents();
       @stack[dest].push(change)


difficulty bump: enemies a little faster

--- public/js/Enemy.coffee
+++ public/js/Enemy.coffee
@@ -4,7 +4,7 @@
 	maxSpeed: 0.1
 	size: 5
 	constructor: (x, y) ->
-		@maxSpeed = 0.05 + Math.random() * 0.1
+		@maxSpeed = 0.05 + Math.random() * 0.2
 		super(x,y)
 	detonationSize: () =>
 		ds = super()


Client: Activity: Feed: Added handleSetChannelResultStateFlag event handler

--- client/activity/lib/flux/stores/channelflagsstore.coffee
+++ client/activity/lib/flux/stores/channelflagsstore.coffee
@@ -23,6 +23,7 @@
     @on actions.SET_CHANNEL_LAST_SEEN_TIME, @handleSetLastSeenTime
     @on actions.SET_MESSAGE_EDIT_MODE, @handleSetMessageEditMode
     @on actions.UNSET_MESSAGE_EDIT_MODE, @handleUnsetMessageEditMode
+    @on actions.SET_CHANNEL_RESULT_STATE_FLAG, @handleSetChannelResultStateFlag

   handleLoadChannel: (channelFlags, { channelId }) ->
@@ -78,6 +79,12 @@
     return channelFlags.setIn [channelId, 'hasEditingMessage'], no

+  handleSetChannelResultStateFlag: (channelFlags, { channelId, resultState }) ->
+
+    channelFlags = helper.ensureChannelMap channelFlags, channelId
+    return channelFlags.setIn [channelId, 'resultListState'], resultState
+
+
 helper =

   ensureChannelMap: (channelFlags, channelId) ->


StackTemplateListController: Use thin arrow

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -95,7 +95,7 @@
   generateStack: (item) ->

     stackTemplate = item.getData()
-    stackTemplate.generateStack (err, stack) =>
+    stackTemplate.generateStack (err, stack) ->

       unless showError err
         kd.singletons.computeController.reset yes


Save the hub_id from the HomePi

Save the hub_id from the HomePi

--- express/routes/collector.coffee
+++ express/routes/collector.coffee
@@ -6,20 +6,25 @@

     jsonData = req.body

-    mongo.db.collection('sensor').count(
+    mongo.db.collection('sensors').count(
         {sensor_id : jsonData.sensor_id}, (err, count) ->
             if err
                 console.log(err)
                 res.send 500

             else if  count < 1
-                console.log "Adding new sensor " + jsonData.sensor_id + " to DB"
-                mongo.db.collection('sensor').insert(
-                    {sensor_id : jsonData.sensor_id, model : jsonData.model }, {w:1}, (err, collection) ->
+                mongo.db.collection('sensors').insert(
+                    {sensor_id : jsonData.sensor_id, model : jsonData.model, hub_id : jsonData.hub_id, created_on : Date.now()}, {w:1}, (err, collection) ->
                         console.log(err)
                 )
         )
-
+    data =
+        sensor_id : jsonData.sensor_id
+        value : jsonData.value
+        model : jsonData.model
+        unit : jsonData.unit
+        created_on : jsonData.created_on
+
     mongo.db.collection('data').insert(
         jsonData, {w:1}, (err, collection) ->
             if err


split common functionality into two parts:

1) requires no currentUser.channels
2) requires currentUser.channels

--- core/app/backbone/controllers/channels_controller.coffee
+++ core/app/backbone/controllers/channels_controller.coffee
@@ -17,12 +17,17 @@
     @restoreChannelView "topic-#{slug_title}", new_callback

   commonTopicViews: (topic) ->
-    channel = topic.existingChannelFor(currentUser)
+    @commonTopicViewsBeforeFetchOfCurrentUsersChannels(topic)
+    @commonTopicViewsAfterFetchOfCurrentUsersChannels(topic)
+
+  commonTopicViewsBeforeFetchOfCurrentUsersChannels: (topic) ->
+    FactlinkApp.leftBottomRegion.close()
+    @showUserProfile currentUser
+
+  commonTopicViewsAfterFetchOfCurrentUsersChannels: (topic) ->
+    channel = topic.existingChannelFor(currentUser) # implicitly uses currentUser.channels
     window.currentChannel = channel
-
-    FactlinkApp.leftBottomRegion.close()
     FactlinkApp.Sidebar.showForChannelsOrTopicsAndActivateCorrectItem(window.Channels, channel, currentUser)
-    @showUserProfile currentUser

   getTopicFacts: (slug_title) ->
     FactlinkApp.mainRegion.show(@channel_views)


Rollback should also revert MMT name

Rollback should also revert MMT name

--- app/assets/javascripts/controllers/manuscript_manager_template_edit_controller.js.coffee
+++ app/assets/javascripts/controllers/manuscript_manager_template_edit_controller.js.coffee
@@ -107,6 +107,7 @@
       else
         @store.unloadAll('taskTemplate')
         @store.unloadAll('phaseTemplate')
+        @get('model').rollback()
         @get('model').reload().then =>
           @reset()
           @send('didRollBack')


Update svg-sprite task

Update svg-sprite task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -61,23 +61,22 @@
   spriteData.css.pipe gulp.dest '_src/styles/sprite'
   return

-config =
-  mode:
-    css:
-      dimensions: '-icon'
-      dest: '../../_src/styles/svg-sprite'
-      sprite: 'sprite.css.svg'
-      bust: false
-      render:
-        styl: true
-    symbol: true
-    symbol: sprite: '../../../_src/styles/svg-sprite/sprite.symbol.svg'
-  svg:
-    xmlDeclaration: false
-    doctypeDeclaration: false
 gulp.task 'svg-sprite', ->
   gulp.src '_src/svg/*.svg'
-  .pipe plugins.svgSprite config
+  .pipe plugins.svgSprite
+    mode:
+      css:
+        dimensions: '-icon'
+        dest: '../../_src/styles/svg-sprite'
+        sprite: 'sprite.css.svg'
+        bust: false
+        render:
+          styl: true
+      symbol: true
+      symbol: sprite: '../../../_src/styles/svg-sprite/sprite.symbol.svg'
+    svg:
+      xmlDeclaration: false
+      doctypeDeclaration: false
   .pipe gulp.dest 'images/svg'
   return

@@ -103,6 +102,6 @@
   gulp.watch '_src/**/*.jade',       ['jade']
   gulp.watch '_src/vendor/**/*.js',  ['uglify']
   gulp.watch '_src/sprite/*',        ['sprite','css']
-  gulp.watch '_src/svg/*.svg',       ['svg-sprite','jade']
+  gulp.watch '_src/svg/*.svg',       ['svg-sprite']
   gulp.watch 'images/**/*.{png,jpg}',['imagemin']
   return


builds app first

builds app first

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -113,10 +113,13 @@
         'icon_256.png': 'icon@16.png'
         'icon_512.png': 'icon@32.png'

-    Object.keys(nameMap).forEach (name) ->
-        gulp.src path.join paths.icons, name
-            .pipe rename nameMap[name]
-            .pipe gulp.dest path.join outapp, 'icons'
+    # gulp 4 requires async notification!
+    new Promise (resolve, reject)->
+        Object.keys(nameMap).forEach (name) ->
+            gulp.src path.join paths.icons, name
+                .pipe rename nameMap[name]
+                .pipe gulp.dest path.join outapp, 'icons'
+        resolve()

 # compile less
 gulp.task 'less', ->
@@ -173,41 +176,45 @@
 buildDeployTask = (platform, arch) ->
     # create a task per platform
     taskname = "deploy:#{platform}-#{arch}"
-    gulp.task taskname, ()->
-        deferred = Q.defer()
-        deploy platform, arch, () ->
-            deferred.resolve()
-        deferred.promise
-    taskname
-
+    tasknameNoDep = "#{taskname}:nodep"
+    # set internal task with _ (does not have dependencies)
+    gulp.task tasknameNoDep, ()->
+        deploy platform, arch
+    # set task with dependencies
+    gulp.task taskname, gulp.series('default', tasknameNoDep)
+    #
+    tasknameNoDep
+
+#
+# task to deploy all
+allNames = []
 #
 # create tasks for different platforms and architectures supported
 platformOpts.map (plat) ->
-    #
     names = []
     archOpts.map (arch) ->
         # create a task per platform/architecture
-        names.push buildDeployTask(plat, arch)
+        taskName = buildDeployTask(plat, arch)
+        names.push taskName
+        allNames.push taskName
     #
     # create arch-independet task
-    gulp.task "deploy:#{plat}", gulp.parallel(names)
-
-#
-# task to deploy all
-gulp.task 'deploy', ->
-    platformOpts.map (plat) ->
-        archOpts.map (arch) ->
-        deploy(plat, arch)
-
-#
-#
-deploy = (platform, arch, fun) ->
+    gulp.task "deploy:#{plat}", gulp.series('default', gulp.parallel(names))
+    #
+gulp.task 'deploy', gulp.series('default', allNames)
+
+#
+#
+deploy = (platform, arch) ->
+    deferred = Q.defer()
     opts = deploy_options
     opts.platform = platform
     opts.arch = arch
     #
     # restriction darwin won't compile ia32
-    return if platform == 'darwin' && arch == 'ia32'
+    if platform == 'darwin' && arch == 'ia32'
+        deferred.resolve()
+        deferred.promise
     #
     # necessary to add a callback to pipe (which is used to signal end of task)
     gulpCallback = (obj) ->
@@ -230,4 +237,5 @@
                 .pipe zip "yakyak-#{platform}-#{arch}-#{json.version}.zip"
                 .pipe gulp.dest outdeploy
                 .pipe gulpCallback ()->
-                    fun()
+                    deferred.resolve()
+    deferred.promise


Revert "Bump version to 0.15.1"

Revert "Bump version to 0.15.1"

This reverts commit 181686fbbc0690c6afc35ab545b574cd4f4fa0b4.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,7 +20,7 @@
     nw = new NwBuilder(
       files: './src/**'
       platforms: [platform]
-      version: '0.15.1'
+      version: '0.14.5-sdk'
       winIco: if process.argv.indexOf('--noicon') > 0 then undefined else './assets-windows/icon.ico'
       macIcns: './assets-osx/icon.icns'
       macZip: true


chore: remove error log

chore: remove error log

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -240,7 +240,6 @@
           bundleSuccess = false
           gutil.log gutil.colors.black.bgRed " [BUNDLING FAILED] (#{suffix}) #{c.name} "
           gutil.log err.message
-          console.log err
           @emit 'end'
         .on 'end', ->
           copyFiles(path.join(c.dest[0], c.name), c.dest[1..])


added event filtering

added event filtering

--- assets/lib/calendar.coffee
+++ assets/lib/calendar.coffee
@@ -6,6 +6,11 @@
 calendarData = {}
 prefix = ''

+## To filter events, use events_filter in _config.yml
+keyword = "{{ site.events_filter }}"
+## To filter by tag only, set events_filter_tags_only: true in _config.yml
+filterByTagOnly = {{ site.events_filter_tags_only | default: false }}
+
 getHash = ->
   if document.location.hash != ''
     hashInfo = document.location.hash.slice(1).split('/')
@@ -14,6 +19,8 @@

 scrollTo = (hash) ->
   setTimeout(->
+    return unless hash.slice(1)
+
     el = document.getElementById(hash.slice(1))

     if el
@@ -47,8 +54,8 @@

   new Date(dateTime).toLocaleFormat(format)

-makeQuickLinks = (allConfs) ->
-  $lis = $('h2.event>a', allConfs).clone().map((el) ->
+makeQuickLinks = (allEvents) ->
+  $lis = $('h2.event>a', allEvents).clone().map((el) ->
     @['title'] = ''
     $('
').append(@)[0]
   )
@@ -61,12 +68,36 @@

     """).append($ul)

+skipEvent = (event) ->
+  return false if keyword == ""
+  return true unless event
+
+  console.log keyword, filterByTagOnly
+
+  if event.tags
+    tags = event.tags.split /[, ]+/
+
+  if keyword
+    regex = new RegExp(keyword, 'i')
+    match_tags = event.tags?.match(regex)
+    match_desc = event.description?.match(regex)
+    match_talk = event.talks?.map((t)-> t.description).join(' ').match(regex)
+
+    result = if filterByTagOnly
+               match_tags
+             else
+               match_tags || match_talk || match_desc
+
+    return !result
+
 processAllEvents = (events) ->
   formatted = []

   for year of events
     for item of events[year]
       event = events[year][item]
+
+      continue if skipEvent(event)

       if event['start']

@@ -101,7 +132,7 @@

   # $list.html('')

-  $allConfs = $('

')
+  $allEvents = $('

')

   for yearLabel, year of data
     continue if yearOnly && +yearLabel != yearOnly
@@ -111,53 +142,55 @@
     else
       ""

-    for label, conf of year
-      continue unless new Date(conf.end) > today || yearOnly
+    for label, event of year
+      continue unless new Date(event.end) > today || yearOnly
+
+      continue if skipEvent(event)

       numberOfEvents += 1

-      confID = prefix + slugify label
-
-      location = if conf.location
+      eventID = prefix + slugify label
+
+      location = if event.location
         """

-
-            #{ conf.location }
+
+            #{ event.location }

         """

-      description = if conf.description
-        """
-
#{ conf.description }

-        """
-
-      confEnd = if conf.end
+      description = if event.description
+        """
+
#{ event.description }

+        """
+
+      eventEnd = if event.end
           """
                   -
-                  #{
-                    humanizeDate conf.end
+                  #{
+                    humanizeDate event.end
                   }
           """
         else
           ''

-      $conf = $("""
-

+      $event = $("""
+

           Back to top

-

-
-                #{ conf.name }
+

+
+                #{ event.name }

-

+

               #{ location || '' }

-                #{
-                  humanizeDate conf.start
-                  }
-                #{ confEnd }
+                #{
+                  humanizeDate event.start
+                  }
+                #{ eventEnd }

               #{ description || '' }

@@ -165,9 +198,9 @@

         """)

-      if conf.talks
-        conf.talks.forEach (talk) ->
-          talkID = confID + '/' + slugify talk.title
+      if event.talks
+        event.talks.forEach (talk) ->
+          talkID = eventID + '/' + slugify talk.title

           speaker = if talk.speaker
             """
@@ -209,13 +242,13 @@

             """)

-          $conf.append($talk)
-
-      $allConfs.append($conf)
-
-  $allConfs.prepend(makeQuickLinks $allConfs)
-
-  $list.html($allConfs.children())
+          $event.append($talk)
+
+      $allEvents.append($event)
+
+  $allEvents.prepend(makeQuickLinks $allEvents)
+
+  $list.html($allEvents.children())

   scrollTo(document.location.hash)


FIX: failing CI test

FIX: failing CI test

Former-commit-id: 6ccc90e1cd3efa72c6a2463c672bb0a496e877f7 [formerly 027f31fb1ac758e205ad7767e763ca5dde98d8d9]
Former-commit-id: 9867cad628594d77241ebf42fda8e4dbd191b14b

--- src/views/CanvasSeqBlock.coffee
+++ src/views/CanvasSeqBlock.coffee
@@ -200,7 +200,8 @@
       if x > @el.width
         break

-    @_appendSelection model: data.model, xZero: xZero, yZero: yZero
+    @_appendSelection model: data.model, xZero: xZero, yZero: yZero, hidden:
+      data.hidden

   render: ->


Recursive indented pretty printing for eval

--- lib/util.coffee
+++ lib/util.coffee
@@ -1,7 +1,7 @@
 c = require('./color').color

 # crappy util for pretty printing objects to the client
-exports.old_print = (o) ->
+old_print = (o) ->
   output = "\n"
   for key, value of o
     if value
@@ -9,9 +9,18 @@
       output += c(value, 'blue bold') + "\n"
   output

+safeString = (s) ->
+  s.replace /\n/g, '\\n'
+
+truncate = (s, length = 25) ->
+  if s.length <= 25
+    s
+  else
+    s[0..length] + '...'
+
 # recursive colorful object output
 # TODO: make it work for arrays and objects
-exports.print = (x, indent = "", color = true) ->
+print = (x, indent = '', prefix = '', color = true) ->
   # possible types:
   #
   # number
@@ -25,23 +34,69 @@
   #   date

   # turns colors off
-  c = ((x) -> x) if color == false
+  k = if color then c else ((x) -> x)

   output = switch typeof x
     when 'number'
-      c x, 'yellow'
+      k x, 'yellow'
     when 'string'
-      c "'#{x}'", 'green'
+      # truncate the string if it's in an object or array
+      # TODO this doesn't quite work (misses inline arrays)
+      if indent == '' and prefix == ''
+        "'" + (k (safeString x), 'green') + "'"
+      else
+        "'" + (k (truncate safeString x), 'green') + "'"
     when 'boolean'
-      c x.toString(), 'magenta'
+      k x.toString(), 'magenta'
     when 'undefined'
-      c 'undefined', 'gray'
+      k 'undefined', 'gray'
     when 'function'
-      c '[Function]', 'cyan'
+      k '[Function]', 'cyan'
     when 'object'
       if x == null
-        c 'null', 'red'
+        k 'null', 'red'
       else
-        c '[Object]', 'blue'
+        if Array.isArray x
+          test = '[ ' + ((x.map (y) -> print y, '', '', false).join ', ') + ' ]'
+          if test.length <= 50 # arbitrary length threshold to force indented mode instead of inline
+            if x.length == 0
+              '[]'
+            else
+              '[ ' + ((x.map (y) -> print y, '', '', color).join ', ') + ' ]'
+          else
+            xs = (x.map (y) -> print y, indent + '  ', '', color)
+            xs[0] = '[ ' + (xs[0].replace indent + '  ', '')
+            xs[xs.length-1] += ' ]'
+            if prefix != ''
+              xs[0] = '\n' + indent + xs[0]
+            xs.join ',\n'
+        else
+          xstest = []
+          for key, value of x
+            xstest.push print value, '', key + ': ', false
+          if xstest.length == 0
+            xsteststr = '{}'
+          else
+            xsteststr = "{ #{xstest.join ', '} }"

-  return indent + output+          if xsteststr.length <= 50
+            xs = []
+            for key, value of x
+              xs.push print value, '', (k key, 'blue') + ': ', color
+            if xs.length == 0
+              '{}'
+            else
+              "{ #{xs.join ', '} }"
+          else
+            xs = []
+            for key, value of x
+              xs.push print value, indent + '  ', (k key, 'blue') + ': ', color
+            xs[0] = '{ ' + (xs[0].replace indent + '  ', '')
+            xs[xs.length-1] += ' }'
+            if prefix != ''
+              xs[0] = '\n' + indent + xs[0]
+            xs.join ',\n'
+
+  return indent + prefix + output
+
+exports.print = print


Add explicit track event for viewing teacher trial

We want to explicitly track views before submit.

--- app/views/TeachersFreeTrialView.coffee
+++ app/views/TeachersFreeTrialView.coffee
@@ -25,6 +25,9 @@
     @listenToOnce @existingRequests, 'sync', =>
       @fetchingData = false
       @render?()
+      existingRequest = @existingRequests.at(0)
+      if existingRequest?.get('status') isnt 'submitted' and existingRequest?.get('status') isnt 'approved'
+        window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel']
     @supermodel.loadCollection(@existingRequests, 'own_trial_requests', {cache: false})

   onClickTextBox: (e) ->


pass html in callPhantom

pass html in callPhantom

--- angular-static-seo.coffee
+++ angular-static-seo.coffee
@@ -4,20 +4,20 @@
 This version adds the ability to call $scope.startedLoading() and $scope.finishedLoading() many times,
 in the event that you have multiple controllers making seperate HTTP requests.
 ###
-(->
+((window, document) ->
 	getModule = (angular) ->
 		angular.module("seo", [])
 			   .run ["$rootScope", ($rootScope) ->
-			   		thingsLoading = 0
+					thingsLoading = 0

-			   		$rootScope.startedLoading = ->
-			   			thingsLoading += 1
+					$rootScope.startedLoading = ->
+						thingsLoading += 1

-			   		$rootScope.finishedLoading = ->
-			   			thingsLoading -= 1
+					$rootScope.finishedLoading = ->
+						thingsLoading -= 1

-			   			if thingsLoading < 1
-			   				$rootScope.htmlReady()
+						if thingsLoading < 1
+							$rootScope.htmlReady()

 					$rootScope.htmlReady = ->

@@ -27,7 +27,7 @@
 							# fire after DOM rendering
 							setTimeout ->
 								if typeof window.callPhantom is "function"
-									window.callPhantom()
+									window.callPhantom rendered: document.documentElement.outerHTML
 							, 0
 				]

@@ -35,4 +35,4 @@
 		define ["angular"], getModule
 	else
 		getModule angular
-)(window)
+)(window, document)


use koding email for registering user

use koding email for registering user

--- workers/social/lib/social/models/group/index.test.coffee
+++ workers/social/lib/social/models/group/index.test.coffee
@@ -523,8 +523,7 @@
       client            = {}
       account           = {}
       groupSlug         = generateRandomString()
-      userFormData      = generateDummyUserFormData
-        email : generateRandomEmail 'gmail.com'
+      userFormData      = generateDummyUserFormData()

       groupData         =
         slug           : groupSlug
@@ -532,6 +531,8 @@
         visibility     : 'visible'
         allowedDomains : ['gmail.com']

+      allowedDomainEmail = generateRandomEmail groupData.allowedDomains[0]
+
       queue = [

         ->
@@ -552,7 +553,7 @@
           # expecting to return false when there are no allowedDomains
           JGroup.create client, groupData, account, (err, group) ->
             expect(err).to.not.exist
-            expect(group.isInAllowedDomain userFormData.email).to.be.ok
+            expect(group.isInAllowedDomain allowedDomainEmail).to.be.ok
             queue.next()

         -> done()


If autosaving association, render the nested relationships of the resources instead of just attributes

--- src/interfaces/json_api.coffee
+++ src/interfaces/json_api.coffee
@@ -228,7 +228,8 @@
               .all(cached: true).map((target) ->
                 output = buildResourceIdentifier(target)
                 if reflection.autosave?()
-                  output['attributes'] = _.omit(target.attributes(), resource.klass().primaryKey)
+                  output['attributes'] = toUnderscored(_.omit(target.attributes(), resource.klass().primaryKey))
+                  output['relationships'] = buildResourceRelationships(target)
                 output
               ).toArray()
       else
@@ -236,7 +237,8 @@
           target = resource.association(reflection.name).reader()
           output = buildResourceIdentifier(target)
           if reflection.autosave?()
-            output['attributes'] = _.omit(target.attributes(), resource.klass().primaryKey)
+            output['attributes'] = toUnderscored(_.omit(target.attributes(), resource.klass().primaryKey))
+            output['relationships'] = buildResourceRelationships(target)

           relationships[s.underscored(reflection.name)] = { data: output }


AppStorage: fix unsetKey not provided callback issue

--- client/Main/AppStorage.coffee
+++ client/Main/AppStorage.coffee
@@ -60,7 +60,7 @@
       delete @_storageData[group]?[key]
       storage.update {
         $unset: pack
-      }, callback
+      }, -> callback?()

   reset: ->
     @_storage = null


[master] frontend id

[master] frontend id

--- frontend/src/coffee/main.coffee
+++ frontend/src/coffee/main.coffee
@@ -1,9 +1,15 @@
 $ ->
   server = 'http://api.myap.ml'
-  jsonPath = 'summary'
+  # id = location.search.match(/[^=]+$/)[0]
   width = Math.min window.innerWidth, 1280

-  d3.json "#{server}/summary", (err, data) ->
+  match = location.search.match /[^=]+$/
+  if match
+    id = match[0]
+  else
+    id = 233
+
+  d3.json "#{server}/summary/#{id}", (err, data) ->
     data = $.map data.data, (d, k) ->
       s = {}
       s.category = k


Simpler component handling

Simpler component handling

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -16,7 +16,10 @@

     # Handle sub-modules
     _.each moduleDef.dependencies, (def) =>
-      _.extend components, @getModuleComponents def
+      depComponents = @getModuleComponents def
+      return if _.isEmpty depComponents
+      _.each depComponents, (cPath, name) ->
+        components[name] = cPath

     # Handle own components
     return components unless moduleDef.noflo


Move log.

Move log.

--- src/coffee/stockimport.coffee
+++ src/coffee/stockimport.coffee
@@ -188,6 +188,7 @@
     entry

   _perform: (stocks, next) ->
+    @_log "Stock entries to process: #{_.size(stocks)}"
     if _.isFunction next
       _.each stocks, (entry) ->
         msg =
@@ -201,7 +202,6 @@
         ElasticIo.returnSuccess msg, next
       Q 'elastic.io messages sent.'
     else
-      @_log "Stock entries to process: #{_.size(stocks)}"
       @_initMatcher().then =>
         @_createOrUpdate stocks


remove facebook as oauth provider (temporarily) because of inscrutable app settings and error. In other words, "GAH!!!!"

--- @client/auth/third_party.coffee
+++ @client/auth/third_party.coffee
@@ -39,7 +39,7 @@

-OAuth_providers = ['facebook', 'google']
+OAuth_providers = ['google'] # ['facebook', 'google']

 # Mixin for authenticating via OAuth


Hotfix: array removal

Hotfix: array removal

--- client/src/app/redWire.coffee
+++ client/src/app/redWire.coffee
@@ -195,11 +195,11 @@
     mapping = ensureIndexMapping(arrayPath, array)
     if key of mapping
       # Remove the value itself
-      array.splice(key, 1)
+      array.splice(mapping[key], 1)

       # Remove the index, and decrement the other indexes
       delete mapping[key]
-      for mappingKey, mappingValue of mapping when mappingKey > key
+      for mappingKey, mappingValue of mapping when parseInt(mappingKey) > parseInt(key)
         mapping[mappingKey] = mappingValue - 1

   handleArrayReplacement = (arrayPath, array, key, newValue) ->


dont send welcome email

dont send welcome email

--- apps/mailchimp_subscribe/routes.coffee
+++ apps/mailchimp_subscribe/routes.coffee
@@ -9,7 +9,7 @@
       email: email: req.body.email
       merge_vars: req.body.mergeVars
       double_optin: false
-      send_welcome: true
+      send_welcome: false
     ).end (err, response) ->
       if (response.ok)
         res.send req.body


Refactor - combine save/publish code into one function

--- client/views/admin/new.coffee
+++ client/views/admin/new.coffee
@@ -66,49 +66,35 @@
       , 2500
   , 100

+save = (tpl, published) ->
+  $title = $(tpl.find('[name=title]'))
+
+  slug = Post.slugify $title.val()
+
+  Meteor.call 'doesBlogExist', slug, (err, exists) ->
+    if not exists
+      post = Post.create
+        title: $title.val()
+        body: tpl.editor.getValue()
+        published: published
+        createdAt: new Date()
+        updatedAt: new Date()
+        publishedAt: new Date()
+        userId: Meteor.userId()
+
+      if post.errors
+        return alert(_(post.errors[0]).values()[0])
+
+      flash 'Publishing...', post
+    else
+      return alert 'Blog with this slug already exists'
+
 Template.blogAdminNew.events

   'click .for-publishing': (e, tpl) ->
     e.preventDefault()
-
-    slug = Post.slugify $('[name=title]').val()
-
-    Meteor.call 'doesBlogExist', slug, (err, exists) ->
-      if not exists
-        post = Post.create
-          title: $('[name=title]').val()
-          body: tpl.editor.getValue()
-          published: true
-          createdAt: new Date()
-          updatedAt: new Date()
-          publishedAt: new Date()
-          userId: Meteor.userId()
-
-        if post.errors
-          return alert(_(post.errors[0]).values()[0])
-
-        flash 'Publishing...', post
-      else
-        return alert 'Blog with this slug already exists'
+    return save(tpl, true)

   'click .for-saving': (e, tpl) ->
     e.preventDefault()
-
-    slug = Post.slugify $('[name=title]').val()
-
-    Meteor.call 'doesBlogExist', slug, (err, exists) ->
-      if not exists
-        post = Post.create
-          title: $('[name=title]').val()
-          body: tpl.editor.getValue()
-          published: false
-          createdAt: new Date()
-          updatedAt: new Date()
-          userId: Meteor.userId()
-
-        if post.errors
-          return alert(_(post.errors[0]).values()[0])
-
-        flash 'Saving...', post
-      else
-        return alert 'Blog with this slug already exists'
+    return save(tpl, false)


Add translations for Tantive IV expansion pack

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -127,6 +127,10 @@
     exportObj.renameShip 'Lambda-Class Shuttle', 'Raumfähre der Lambda-Klasse'
     # Move GR-75 Medium Transport to Medium-Transporter GR-75
     exportObj.renameShip 'GR-75 Medium Transport', 'Medium-Transporter GR-75'
+    # Move CR90 Corvette (Fore) to CR90-Korvette (Bug)
+    exportObj.renameShip 'CR90 Corvette (Fore)', 'CR90-Korvette (Bug)'
+    # Move CR90 Corvette (Aft) to CR90-Korvette (Heck)
+    exportObj.renameShip 'CR90 Corvette (Aft)', 'CR90-Korvette (Heck)'

     pilot_translations =
         "Wedge Antilles":
@@ -319,10 +323,10 @@
             name: '"Geflüster"'
             text: """Nachdem du mit einem Angriff getroffen hast, darfst du deinem Schiff 1 Fokusmarker geben."""
         "CR90 Corvette (Fore)":
-            name: "CR90 Corvette (Fore)"
-            text: """When attacking with your primary weapon, you may spend 1 energy to roll 1 additional attack die."""
+            name: "CR90-Korvette (Bug)"
+            text: """Wenn du mit deinen Primärwaffen angreifst, kannst du 1 Energie ausgeben, um 1 zusätzlichen Angriffswürfel zu bekommen"""
         "CR90 Corvette (Aft)":
-            name: "CR90 Corvette (Aft)"
+            name: "CR90-Korvette (Heck)"
         "Wes Janson":
             text: """Nachdem du einen Angriff durchgeführt hast, darfst du 1 Fokus-, Ausweich- oder blauen Zielerfassungsmarker vom Verteidiger entfernen."""
         "Jek Porkins":
@@ -342,10 +346,10 @@
             name: "Unspoiled PS5 B-Wing Pilot"
             text: """This card has not yet been revealed."""
         "CR90 Corvette (Crippled Aft)":
-            name: "CR90 Corvette (Crippled Aft)"
-            text: """You cannot choose or execute (%STRAIGHT% 4), (%BANKLEFT% 2), or (%BANKRIGHT% 2) maneuvers."""
+            name: "CR90-Korvette (Heck-Lahmgelegt)"
+            text: """Folgende Manöver darfst du weder wählen noch ausführen: (%STRAIGHT% 4), (%BANKLEFT% 2), or (%BANKRIGHT% 2) ."""
         "CR90 Corvette (Crippled Fore)":
-            name: "CR90 Corvette (Crippled Fore)"
+            name: "CR90-Korvette (Bug-Lahmgelegt)"
         "Dash Rendar":
             text: """You may ignore obstacles during the Activation phase and when performing actions."""
         "Rear Admiral Chiraneau":
@@ -493,7 +497,7 @@
             name: "Opportunist"
             text: """Wenn du angreifst und der Verteidiger keine Fokusmarker oder Ausweichmarker hat, kannst du einen Stressmarker nehmen, um einen zusätzlichen Angriffswürfel zu erhalten.

Du kannst diese Fähigkeit nicht nutzen, wenn du einen Stressmarker hast."""
         "Comms Booster":
-            name: "Comms Booster"
+            name: "Kommunikationsverstärker"
             text: """Energie: Gib 1 Energie aus, um sämtliche Stressmarker von einem freundlichen Schiff in Reichweite 1-3 zu entfernen. Dann erhält jenes Schiff 1 Fokusmarker."""
         "Slicer Tools":
             name: "Hackersoftware"
@@ -530,22 +534,22 @@
             text: """Nachdem du ein Schiff in Reichweite 2 und innerhalb deines Feuerwinkels angegriffen hast, erhält es 1 Stressmarker."""
         "R2-D2 (Crew)":
             name: "R2-D2 (Crew)"
-            text: """At the end of the End phase, if you have no shields, you may recover 1 shield and roll 1 attack die.  On a %HIT% result, randomly flip 1 of your facedown Damage cards faceup and resolve it."""
+            text: """Wenn du am Ende der Endphase keine Schilde hast, darfst du 1 Schild wieder aufladen und 1 Angriffswürfel werfen. Bei %HIT% musst du 1 deiner verdeckten Schadenskarten (zufällig gewählt) umdrehen und abhandeln."""
         "C-3PO":
             name: "C-3PO"
-            text: """Once per round, before you roll 1 or more defense dice, you may guess aloud a number of %EVADE% results.  If you roll that many %EVADE% results (before modifying dice), add 1 %EVADE% result."""
+            text: """Einmal pro Runde darfst du, bevor du 1 oder mehrere Verteidigungswürfel wirfst, laut raten, wie viele %EVADE% du würfeln wirst. Wenn du richtig geraten hast (bevor die Ergebnisse modifiziert werden), wird 1 %EVADE% hinzugefügt."""
         "Single Turbolasers":
-            name: "Single Turbolasers"
-            text: """Attack (Energy): Spend 2 energy from this card to perform this attack.  The defender doubles his agility value against this attack.  You may change 1 of your %FOCUS% results to a %HIT% result."""
+            name: "Einzelne Turbolasers"
+            text: """Angriff (Energie): gib 2 Energie von dieser Karte aus, um mit dieser Sekundärwaffe anzugreifen. Der Verteidiger verwendet zum Verteidigen seinen doppelten Wendigkeitswert. Du darfst 1 deiner %FOCUS% in ein %HIT% ändern."""
         "Quad Laser Cannons":
-            name: "Quad Laser Cannons"
-            text: """Attack (Energy): Spend 1 energy from this card to perform this attack.  If this attack does not hit, you may immediately spend 1 energy from this card to perform this attack again."""
+            name: "Vierlings-Laserkanone"
+            text: """Angriff (Energie): Gib 1 Energie von dieser Karte aus, um mit dieser Sekundärwaffe anzugreifen. Wenn der Angriff verfehlt, kannst du sofort 1 Energie von dieser Karte ausgeben, um den Angriff zu wiederholen."""
         "Tibanna Gas Supplies":
             name: "Tibanna-Gas-Vorräte"
             text: """Energie: Du kannst diese Karte ablegen, um 3 Energie zu erzeugen."""
         "Ionization Reactor":
-            name: "Ionization Reactor"
-            text: """Energy: Spend 5 energy from this card and discard this card to cause each other ship at Range 1 to suffer 1 damage and receive 1 ion token."""
+            name: "Ionenreaktor"
+            text: """Energie: Gib 5 Energie von dieser Karte aus und lege sie ab, damit jedes andere Schiff in Reichweite 1 einen Schaden nimmt und einen Ionenmarker bekommt."""
         "Engine Booster":
             name: "Nachbrenner"
             text: """Unmittelbar bevor du dein Manöverrad aufdeckst, kannst du 1 Energie ausgeben, um ein weißes (%STRAIGHT% 1)-Manöver auszuführen. Wenn es dadurch zur Überschneidung mit einem anderen Schiff käme, darfst du diese Fähigkeit nicht nutzen."""
@@ -596,23 +600,30 @@
             name: "Störsender (Fracht)"
             text: """Wenn du die Aktion Störsignal durchführst, wähle 1 feindliches Schiff ohne Stressmarker in Reichweite 1 des vom Störsignal betroffenen Schiffs. Das gewählte Schiff erhält 1 Stressmarker."""
         "Han Solo":
-            text: """When attacking, if you have a target lock on the defender, you may spend that target lock to change all of your %FOCUS% results to %HIT% results."""
+            text: """Wenn du angreifst und den Verteidiger in Zielerfassung hast, kannst du diese Zielerfassung ausgeben, um all deine gewürfelten %FOCUS% in %HIT% zu ändern."""
         "Leia Organa":
-            text: """At the start of the Activation phase, you may discard this card to allow all friendly ships that reveal a red maneuver to treat that maneuver as a white maneuver until the end of the phase."""
+            text: """Zu Beginn der Aktivierungsphase kannst du diese Karte ablegen, damit alle freundlichen Schiffe, die ein rotes Manöver aufdecken, dieses bis zum Ende der Phase wie ein weißes Manöver behandeln dürfen."""
         "Raymus Antilles":
-            text: """At the start of the Activation phase, choose 1 enemy ship at Range 1-3.  You may look at that ship's chosen maneuver.  If the maneuver is white, assign that ship 1 stress token."""
+            text: """Wähle zu Beginn der Aktivierungsphase 1 feindliches Schiff in Reichweite 1-3. Du kannst dir das gewählte Manöver dieses Schiffes ansehen. Wenn es weiß ist, bekommt dieses Schiff 1 Stressmarker."""
         "Gunnery Team":
-            text: """Once per round, when attacking with a secondary weapon, you may spend 1 energy to change 1 of your blank results to a %HIT% result."""
+            name: "Bordschützenteam"
+            text: """Einmal pro runde kannst du beim Angreifen mit einer Sekundärwaffe 1 Energie ausgeben, um 1 gewürfelte Leerseite in ein %HIT% zu ändern."""
         "Sensor Team":
-            text: """When acquiring a target lock, you may lock onto an enemy ship at Range 1-5 instead of 1-3."""
+            name: "Sensortechnikerteam"
+            text: """Du kannst feindliche Schiffe in Reichweite 1-5 in die Zielerfassung nehmen (anstatt in Reichweite 1-3)."""
         "Engineering Team":
-            text: """During the Activation phase, when you reveal a %STRAIGHT% maneuver, gain 1 additional energy during the "Gain Energy" step."""
+            name: "Ingenieurteam"
+            text: """Wenn du in der Aktivierungsphase ein %STRAIGHT% Manöver aufdeckst, bekommst du im Schritt "Energie gewinnen" 1 zusätzlichen Energiemarker."""
         "Lando Calrissian":
             text: """Action: Roll 2 defense dice.  For each %FOCUS% result, assign 1 focus token to your ship.  For each %EVADE% result, assign 1 evade token to your ship."""
         "Mara Jade":
             text: """At the end of the Combat phase, each enemy ship at Range 1 that does not have a stress token receives 1 stress token."""
         "Fleet Officer":
             text: """Action: Choose up to 2 friendly ships within Range 1-2 and assign 1 focus token to each of those ships.  Then receive 1 stress token."""
+        "Targeting Coordinator":
+            name: "Zielkoordinator"
+            text: """Energie: Du kannst 1 Energie ausgeben, um 1 freundliches Schiff in Reichweite1-2 zu wählen. Nimm dann ein Schiff in die Zielerfassung und gibt den blauen Zielerfassungsmarker dem gewählten Schiff."""
+

     modification_translations =
         "Stealth Device":
@@ -667,13 +678,15 @@
             ship: "TIE-Abfangjäger"
             text: """Du kannst bis zu 2 verschiedene Modifikationen verwenden (statt einer).

Du kannst diese Karte nicht verwenden, wenn der Pilotenwert "4" oder kleiner ist."""
         "Dodonna's Pride":
-            name: "Dodonna's Pride"
-            text: """When you perform a coordinate action, you may choose 2 friendly ships (instead of 1).  Those ships may each perform 1 free action."""
+            name: "Dodonnas Stolz"
+            ship: "CR90-Korvette (Bug)"
+            text: """Wenn du die Aktion Koordination durchführst, kannst du 2 freundliche Schiffe wählen (anstatt 1). Jedes dieser Schiffe darf 1 freie Aktion durchführen."""
         "A-Wing Test Pilot":
             name: "A-Wing Test Pilot"
             text: """Your upgrade bar gains 1 %ELITE% upgrade icon.

You cannot equip 2 of the same %ELITE% Upgrade cards.  You cannot equip this if your pilot skill value is "1" or lower."""
         "Tantive IV":
-            text: """Your fore section upgrade bar gains 1 additional %CREW% and 1 additional %TEAM% upgrade icon."""
+            ship: "CR90-Korvette (Bug)"
+            text: """Die Aufwertungsleiste deiner Bugsektion erhält 1 zusätzliches %CREW% und 1 zusätzliches %TEAM% ."""
         "Bright Hope":
             ship: "Medium-Transporter GR-75"
             text: """Wenn neben deiner Bugsektion ein Verstärkungsmarker liegt, fügt er 2 %EVADE% hinzu (anstatt 1)."""
@@ -684,7 +697,9 @@
             ship: "Medium-Transporter GR-75"
             text: """Bei der Aktion Störsignal kannst du ein feindliches Schiff in Reichweite 1-3 (statt 1-2) wählen."""
         "Jaina's Light":
-            text: """When defending, once per attack, if you are dealt a faceup Damage card, you may discard it and draw another faceup Damage card."""
+            name: "Jainas Licht"
+            ship: "CR90-Korvette (Bug)"
+            text: """Wenn du verteidigst, darfst du einmal pro Angriff eine soeben erhaltene, offene Schadenskarte ablegen und dafür eine neue offene Schadenskarte ziehen."""
         "Outrider":
             text: """While you have a %CANNON% Upgrade card equipped, you cannot perform primary weapon attacks and you may perform %CANNON% secondary weapon attacks against ships outside your firing arc."""


Redirect students and teachers from /account/subscription

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -29,7 +29,7 @@
     'account/settings': go('account/AccountSettingsRootView')
     'account/unsubscribe': go('account/UnsubscribeView')
     'account/payments': go('account/PaymentsView')
-    'account/subscription': go('account/SubscriptionView')
+    'account/subscription': go('account/SubscriptionView', { redirectStudents: true, redirectTeachers: true })
     'account/invoices': go('account/InvoicesView')
     'account/prepaid': go('account/PrepaidView')


latex table builder creating well formed document

--- queries/_runner.coffee
+++ queries/_runner.coffee
@@ -12,47 +12,45 @@
   data = opts.data
   columns = opts.columns

+  warning = "% auto compiled by tom\n\n"
+
   aligns = _.pluck columns, "align"
   columnAligns = "| " + aligns.join(" | ") + " |"

-  preamble = "\\begin{center}\\begin{longtable}{ #{columnAligns} }\\hline"
+  preamble = "\\begin{center} \n \\begin{longtable}{ #{columnAligns} } \n \\hline \n"

   for column, i in columns
     align = if i is 0 then "|c|" else "c|"
     preamble += "\\multicolumn{1}{#{align}}{\\textbf{#{column.title}}}"
-    preamble += " & " unless i is columns.length - 1
+    preamble += " & \n" unless i is columns.length - 1

-  preamble += "\\\\ \\hline \\endfirsthead"
+  preamble += "\\\\ \\hline \n \\endfirsthead \n\n"

-  longtable = "
-    \\multicolumn{#{columns.length}}{c}%
-    {{\\bfseries \\tablename\ \\thetable{} #{longtableHeadText}}} \\\\
-    \\hline "
+  longtable = "\\multicolumn{#{columns.length}}{c} \n"
+  longtable += "{{\\bfseries \\tablename \\thetable{} #{longtableHeadText}}} \\\\ \n"
+  longtable += "\\hline \n"

   for column, i in columns
     align = if i is 0 then "|c|" else "c|"
-    longtable += "\\multicolumn{1}{#{align}}{\\textbf{#{column.title}}}"
-    longtable += " & " unless i is columns.length - 1
+    longtable += "\\multicolumn{1}{#{align}}{\\textbf{#{column.title}}} "
+    longtable += " & \n" unless i is columns.length - 1

-  longtable += "
-    \\\\ \\hline \\endhead
-    \\hline \\multicolumn{#{columns.length}}{|r|}{{#{longtableFootText}}} \\\\ \\hline
-    \\endfoot \\hline \\hline \\endlastfoot"
+  longtable += "\\\\ \\hline \n \\endhead \n\n"
+  longtable += "\\hline \\multicolumn{#{columns.length}}{|r|}{{#{longtableFootText}}} \\\\ \\hline \n"
+  longtable += "\\endfoot\n \\hline\n \\endlastfoot\n\n"

-  body = "\\hline"
-  console.log "data: ", data
+  body = "\\hline\n"
   for row in data
-    console.log "row: ", row
     for column, j in columns
-      console.log "j: ", j
-      console.log "column: ", column
       body += row[column.property]
-      body += if j is columns.length - 1 then " \\" else " & "
-    body += "\\hline"
+      body += if j is columns.length - 1 then " \\\\ \n" else " & "
+    body += "\\hline\n"

-  footer = "\\end{longtable} \\end{center}"

-  fullTable = preamble + longtable + body + footer
+  footer = "\n\n"
+  footer += "\\end{longtable}\n \\end{center}"
+
+  fullTable = warning + preamble + longtable + body + footer
   return fullTable

 async.forEachSeries queryDirs, (dir, forEachNext) ->
@@ -66,9 +64,8 @@
         data: rows
         columns: query.columns

-      latexFile = "#{__dirname}/../parts/tables/" + stats.name.split(".coffee")[0] + ".tex"
-
-      fs.writeFile latexFile, latexTable, "utf8", (err) ->
+      fileName = "#{__dirname}/../parts/tables/" + stats.name.split(".coffee")[0] + ".tex"
+      fs.writeFile fileName, latexTable, "utf8", (err) ->
         next()

   walker.on "end", ->


Refator Link model

Refator Link model

--- app/models/link.coffee
+++ app/models/link.coffee
@@ -1,7 +1,7 @@
 MovesPath = require('models/moves_path')
 SimplifiesSegments = require('models/simplifies_segments')
+#Spine = require('spine')

-#Spine = require('spine')
 class Link extends Spine.Model
   @configure "Link", "sourceId", "targetId", "segments", "strokeColor", "strokeStyle"
   @extend Spine.Model.Local
@@ -15,13 +15,6 @@
   updateSegments: (segments) =>
     @segments = new SimplifiesSegments().for(segments)

-  toPath: =>
-    new paper.Path(@toSegments())
-
-  toSegments: =>
-    for s in @segments
-      new paper.Segment(s.point, s.handleIn, s.handleOut)
-
   reconnectTo: (nodeId, offset) =>
     mover = new MovesPath(@toPath(), offset)
     mover.moveStart() if nodeId is @sourceId
@@ -29,4 +22,11 @@
     @updateSegments(mover.finalise().segments)
     @save()

+  toPath: =>
+    new paper.Path(@toSegments())
+
+  toSegments: =>
+    for s in @segments
+      new paper.Segment(s.point, s.handleIn, s.handleOut)
+
 module.exports = Link


add selection save/restore test

add selection save/restore test

--- test/unit/core/selection.coffee
+++ test/unit/core/selection.coffee
@@ -293,4 +293,27 @@
       )
     )
   )
+
+  describe('save / restore selection', ->
+    it('focus on another input', (done) ->
+      @container.innerHTML = '<input type="textbox">
0123
'
+      @quill = new Quill(@container.lastChild)
+      @quill.setSelection(2, 3)
+      @container.firstChild.focus()
+      _.defer( =>
+        expect(@quill.editor.selection.checkFocus()).not.toBeTruthy()
+        expect(@quill.getSelection()).not.toBeTruthy()
+        savedRange = @quill.editor.selection.getRange(true)
+        expect(savedRange).toBeTruthy()
+        expect(savedRange.start).toEqual(2)
+        expect(savedRange.end).toEqual(3)
+        @quill.focus()
+        range = @quill.getSelection()
+        expect(range).toBeTruthy()
+        expect(range.start).toEqual(2)
+        expect(range.end).toEqual(3)
+        done()
+      )
+    )
+  )
 )


Syntax fix

Syntax fix

--- app/models/user.coffee
+++ app/models/user.coffee
@@ -187,7 +187,7 @@

 UserSchema.methods.getNotifications = (limit, cb) ->
 	self = @
-	return cb(null, { items:[]. last_seen: Date.now(), last_update: 0 })
+	return cb(null, { items:[], last_seen: Date.now(), last_update: 0 })
 	if @notification_chunks.length is 0
 		return cb(null, { items: [], last_seen: Date.now() })
 	# TODO: Use cache here if last_sent_notification < last_seen_notifications


#create @功能 #doing 允许通过commit创建issue，并处理状态

--- biz/commit.coffee
+++ biz/commit.coffee
@@ -7,6 +7,7 @@
 _async = require 'async'
 _Issue = require './issue'
 _ = require 'underscore'
+_common = require '../common'

 #统一处理log，便于可以统一禁掉
 _log = (log)->
@@ -18,38 +19,64 @@
     super

   #分析commit message中的标签，来关联对应的issue
-  analysisCommitMessage: (message, member_id, cb)->
+  analysisCommitMessage: (project_id, message, member_id, cb)->
+    self = this
     #提取issue_id
     issue_id = if message.match /#(\d+)/i then RegExp.$1 else 0
     #提取done标签是否存在
     isDone = /#(done|ok)/i.test(message)
-    #提取new标签
-    isNew = /#new/i.test(message)
+    #提取创建标签
+    isCreate = /#create/i.test(message)
     #提取doing标签
     isDoing = /#doing/i.test(message)
-    _log "issue id -> #{issue_id}, done: #{isDone}, new: #{isNew}, doing: #{isDoing}"
+    _log "issue id -> #{issue_id}, done: #{isDone}, new: #{isCreate}, doing: #{isDoing}"

     queue = []
     queue.push(
       (done)->
-        done null if not isNew
-        #如果包含isNew，则创建一个issue
+        #必须满足条件
+        done null, 0 if not (isCreate and project_id and member_id)
+        #如果包含isCreate，则创建一个issue
+        #提取标签
+        tag =  if message.match /@(.+)\s/ then RegExp.$1 else 'new'
+        tag = _common.checkTag tag
         #替换掉message中的标签
         data =
-          title: message.replace /#(new|doing|done|ok)/ig, ''
+          title: message.replace(/#(new|doing|done|ok)/ig, '').replace(/@(.+)\s/, '')
           status: if isDoing then 'doing' else 'new'
           member_id: member_id
           owner: member_id
           timestamp: new Date()
-          tag: ''
+          tag: tag
+          project_id: project_id

+        self.save data, (err, id)->
+          _log "创建新的issue，id -> #{id}"
+          issue_id = id
+          done err
     )
-    #如果没有issue_id或没有完成，则不做任何处理
-    return cb null, issue_id if not done

-    #如果有done标签，则完成这个issue
-    issue = new _Issue(@member)
-    issue.finishedIssue issue_id, ()-> cb null, issue_id
+    #处理doing的状态
+    queue.push(
+      (done)->
+        #没有获得issue id，不处理
+        return cb null if not (issue_id and isDoing and member_id)
+        issue = new _Issue(member_id: member_id)
+        data = id: issue_id, status: 'doing'
+        issue.save data, done
+    )
+
+    #处理done
+    queue.push(
+      (done)->
+        return cb null if not (issue_id and isDone and member_id)
+        #如果有done标签，则完成这个issue
+        issue = new _Issue(@member)
+        issue.finishedIssue issue_id, ()-> cb null, issue_id
+    )
+
+    _async.waterfall queue, cb
+

   #根据git用户名查找对应的用户id
   findMemberWithGitUser: (gitUser, cb)->
@@ -74,7 +101,7 @@
       (member_id, done)->
         _log "#{commit.author.email}->#{member_id}"
         #分析message中的信息，例如关于到issue，或者完成某个issue等
-        self.analysisCommitMessage commit.message, member_id, (err, issue_id)->
+        self.analysisCommitMessage projectId, commit.message, member_id, (err, issue_id)->
           _log "#{commit.message}"
           done err, member_id, issue_id
     )


notes on indexing large amount of gists

notes on indexing large amount of gists

--- elasticsearch.coffee
+++ elasticsearch.coffee
@@ -191,4 +191,9 @@
   gistMeta = JSON.parse fs.readFileSync(metaFile).toString()
   console.log gistMeta.length, "gists"

+  # I started running into request timeouts and memory errors
+  # when trying to do all 11k at once. I realized I could skip ones
+  # already indexed by slicing the array past whats already been indexed
+  # (gist-meta is an ordered array, new gists are appended to it)
+  #async.eachLimit gistMeta.slice(8500), 20, gistParser, done
   async.eachLimit gistMeta, 20, gistParser, done


invisibles/editor settings

invisibles/editor settings

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -9,12 +9,16 @@
   editor:
     invisibles:
       eol: " "
+      space: " "
     fontFamily: "Hasklig"
     softWrap: true
     showInvisibles: true
     zoomFontWhenCtrlScrolling: false
+    scrollPastEnd: true
+    showIndentGuide: true
   linter:
     errorPanelHeight: 263
+    showErrorPanel: false
   "linter-rubocop":
     command: "/home/alex/.gem/ruby/2.2.0/bin/rubocop"
   "file-icons":


Fix #125

Fix #125

--- src/include/repository.coffee
+++ src/include/repository.coffee
@@ -92,8 +92,10 @@
       ,
         include: '#comma'
       ,
+        include: '#infix_op'
+      ,
         name: 'meta.other.constructor-list.haskell'
-        begin: /{rb}\s*\(/
+        begin: /\(/
         end: /\)/
         patterns: [
           { include: '#comments' }
@@ -107,8 +109,6 @@
           }
           {include: '#infix_op'}
         ]
-      ,
-        include: '#infix_op'
     ]
   module_name:
     name: 'support.other.module.haskell'


Add symbols-tree-view config (disabled)

Add symbols-tree-view config (disabled)

--- etc/atom/config.cson
+++ etc/atom/config.cson
@@ -16,6 +16,7 @@
   core:
     disabledPackages: [
       "Zen"
+      "symbols-tree-view"
     ]
     themes: [
       "atom-dark-ui"
@@ -33,3 +34,5 @@
   linter:
     lintOnChange: false
   "linter-jshint": {}
+  "symbols-tree-view":
+    autoToggle: true


 Merge branch 'master.terabyte' into 'master'

Master.Terabyte

--- client/Intro/tbcampaignhomepageview.coffee
+++ client/Intro/tbcampaignhomepageview.coffee
@@ -10,7 +10,7 @@
     leftInByte = @getData().content.diskSpaceLeftMB * 1024 * 1024
     leftInTB   = KD.utils.formatBytesToHumanReadable leftInByte, 3
     [left]     = leftInTB.split " "
-    left       = "99.99"  if left is "100.00"
+    left       = "99.999"  if left is "100.000"
     [tb, gb]   = left.split "."
     tb         = ["0", tb.first]  if tb.length is 1


removed obsolete isDashboard handlebars helper

--- client/views/instances/instanceView.coffee
+++ client/views/instances/instanceView.coffee
@@ -33,7 +33,6 @@
       'collapse-down'
     else
       'exclamation-sign'
-  isDashboard: -> @meta.appName == "dashboard"
   showProgressbar: -> "#{@meta.state}".match /loading|activating|pulling|stopping/
   totalSteps: -> @meta.totalSteps
   progress: -> @meta.progress


When a test is still running, mark its length as "still running"

--- js/inner.coffee
+++ js/inner.coffee
@@ -313,8 +313,10 @@
         Circle.time.as_time_since(@start_time())

     @duration = @komp () =>
-      if @start_time()
+      if @build_time_millis
         Circle.time.as_duration(@build_time_millis)
+      else
+        "still running"

     @branch_in_words = @komp =>
       return "(unknown)" unless @branch


Add pagination

Add pagination

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -3,7 +3,7 @@
     play: false
     slide_speed: 'normal'
     slide_easing: 'linear'
-    pagination: false
+    pagination: true
     hashchange: false
     scrollable: true
     classes:
@@ -12,14 +12,15 @@
       pagination: 'slides-pagination'
   , options

-  $window    = $(window)
-  $container = $(".#{@options.classes.container}")
-  $children  = $container.children()
-  $control   = $('<div>', class: 'slides-control')
-  multiplier = 1
-  init       = false
-  width      = $window.width()
-  height     = $window.height()
+  $window     = $(window)
+  $container  = $(".#{@options.classes.container}")
+  $children   = $container.children()
+  $pagination = $("<nav>", class: @options.classes.pagination)
+  $control    = $('<div>', class: 'slides-control')
+  multiplier  = 1
+  init        = false
+  width       = $window.width()
+  height      = $window.height()

   # Private
   initialize = =>
@@ -33,6 +34,7 @@

     setupContainers()
     setupChildren()
+    addPagination()

     @start()
     this
@@ -55,6 +57,21 @@

     adjustSlidesSize $children

+  addPagination = =>
+    return unless @options.pagination
+    $(el).append($pagination)
+
+    $children.each (i) ->
+      addPaginationItem(i)
+
+  addPaginationItem = (i) =>
+    unless i >= 0
+      i = @size() - 1 # size is not zero indexed
+
+    $pagination.append $("<a>",
+      href: "##{i}"
+    )
+
   loadImage = ($img, callback) =>
     $("<img>",
         src: $img.attr('src')
@@ -102,8 +119,6 @@

       adjustImagePosition $('img', this).not('.keep-original')

-    # $container.trigger('slides.sized')
-
   findMultiplier = =>
     if @size() == 1 then 1 else 3

@@ -163,6 +178,11 @@
         .css
           left: upcoming_position
           display: 'block'
+
+    $pagination.children()
+      .removeClass('current')
+      .eq(upcoming_slide)
+        .addClass('current')

     $control.animate
       useTranslate3d: @mobile


Use a single Fusion Tables SELECT request to fetch entire text collection

--- src/js/cts-cite-driver.coffee
+++ src/js/cts-cite-driver.coffee
@@ -85,17 +85,14 @@
   $(urn_selector).append source_text
   add_translations(urn)

-get_passage = (urn) ->
-  console.log("get_passage #{urn}")
-  fusion_tables_query "SELECT * FROM #{cts_cite_collection_driver_config['cts_endpoint']} WHERE URN = #{fusion_tables_escape(urn)}", (fusion_tables_result) ->
-    # console.log fusion_tables_result
-    passage = fusion_tables_result.rows[0]
-    request_urn = passage[0]
-    perseus = passage[1]
-    text = passage[2]
-    sol = passage[3]
-    head = urn_to_head(request_urn)
-    set_cts_text(request_urn, head, text, perseus, sol)
+# sets passage from Fusion Tables result row
+set_passage = (passage) ->
+  request_urn = passage[0]
+  perseus = passage[1]
+  text = passage[2]
+  sol = passage[3]
+  head = urn_to_head(request_urn)
+  set_cts_text(request_urn, head, text, perseus, sol)

 show_all = ->
   $('#toggle_group button').removeClass('active')
@@ -128,15 +125,15 @@
   $('#translation_container').append $('<ul>').attr('id','valid_urns')
   translated_urns = 0
   for urn in valid_urns
-    urn_li = $('<li>').attr('id',urn_to_id(urn)).text(urn)
+    urn_li = $('<li>').attr('id',urn_to_id(urn[0])).text(urn[0])
     $('#valid_urns').append urn_li

-    if localStorage["#{urn}[head]"]?
-      set_cts_text(urn, localStorage["#{urn}[head]"], localStorage["#{urn}[body]"], localStorage["#{urn}[perseus]"], localStorage["#{urn}[sol]"])
+    if localStorage["#{urn[0]}[head]"]?
+      set_cts_text(urn[0], localStorage["#{urn[0]}[head]"], localStorage["#{urn[0]}[body]"], localStorage["#{urn[0]}[perseus]"], localStorage["#{urn[0]}[sol]"])
     else
-      get_passage(urn)
+      set_passage(urn)

-    if cite_collection_contains_urn(urn)
+    if cite_collection_contains_urn(urn[0])
       translated_urns += 1

   progress = translated_urns/valid_urns.length * 100.0
@@ -161,8 +158,8 @@
 get_valid_reff = (urn, callback = null) ->
   console.log('get_valid_reff')
   # WHERE URN STARTS WITH '#{urn}'
-  fusion_tables_query "SELECT URN FROM #{cts_cite_collection_driver_config['cts_endpoint']} WHERE URN STARTS WITH '#{urn}'", (fusion_tables_result) ->
-    valid_urns = (urn[0] for urn in fusion_tables_result.rows)
+  fusion_tables_query "SELECT * FROM #{cts_cite_collection_driver_config['cts_endpoint']} WHERE URN STARTS WITH '#{urn}'", (fusion_tables_result) ->
+    valid_urns = fusion_tables_result.rows
     # console.log(valid_urns)
     callback() if callback?
   , ->


Surface editing capabilities.

Surface editing capabilities.

--- packages/artificialengines-pages/reality/chemistry/materials/scattering/scattering.coffee
+++ packages/artificialengines-pages/reality/chemistry/materials/scattering/scattering.coffee
@@ -1,6 +1,7 @@
 AM = Artificial.Mirage
 AR = Artificial.Reality
 AS = Artificial.Spectrum
+AC = Artificial.Control

 class AR.Pages.Chemistry.Materials.Scattering extends AM.Component
   @initializeDataComponent()
@@ -10,6 +11,8 @@

   onCreated: ->
     super arguments...
+
+    AC.Keyboard.initialize()

     @app = @ancestorComponent Retronator.App
     @app.addComponent @
@@ -29,11 +32,11 @@
     @scatteringEventsCount = new ReactiveField 6

     @lightRayDirection = new ReactiveField new THREE.Vector3(0, 1, 0).normalize()
-    @beamWidth = new ReactiveField 30
+    @beamWidth = new ReactiveField 1

     # Prepare rendering properties.
     @schematicView = new ReactiveField false
-    @exposureValue = new ReactiveField 0
+    @exposureValue = new ReactiveField -8

     @_initialize()

@@ -44,15 +47,47 @@

   events: ->
     super(arguments...).concat
-      'mousemove .render-area canvas': @mouseMoveRenderAreaCanvas
-
-  mouseMoveRenderAreaCanvas: (event) ->
+      'mousemove .render-area canvas': @onMouseMoveRenderAreaCanvas
+
+  onMouseMoveRenderAreaCanvas: (event) ->
     return unless surfaceHelpers = @surfaceHelpers()
-
-    mousePosition = new THREE.Vector3 event.offsetX, event.offsetY, 0
-    direction = surfaceHelpers.rayTarget.clone().sub(mousePosition).normalize()
-
-    @lightRayDirection direction
+    keyboardState = AC.Keyboard.getState()
+
+    if keyboardState.isKeyDown AC.Keys.m
+      mousePosition = new THREE.Vector3 event.offsetX, event.offsetY, 0
+      direction = surfaceHelpers.rayTarget.clone().sub(mousePosition).normalize()
+
+      @lightRayDirection direction
+
+    else
+      surfaceCanvas = @surfaceCanvas()
+      size = @size()
+
+      surfaceCanvas.context.fillStyle = 'white'
+
+      if keyboardState.isKeyDown AC.Keys[1]
+        brushWidth = 1
+
+      else if keyboardState.isKeyDown AC.Keys[2]
+        brushWidth = 3
+
+      else if keyboardState.isKeyDown AC.Keys[3]
+        brushWidth = 5
+
+      else if keyboardState.isKeyDown AC.Keys[4]
+        brushWidth = 7
+
+      if keyboardState.isKeyDown AC.Keys.e
+        surfaceCanvas.context.fillStyle = 'black'
+
+      if brushWidth
+        x = Math.floor(event.offsetX / size.width * surfaceCanvas.width) + 0.5
+        y = Math.floor(event.offsetY / size.height * surfaceCanvas.height) + 0.5
+        h = brushWidth / 2
+
+        surfaceCanvas.context.fillRect x - h, y - h, brushWidth, brushWidth
+
+        @surfaceCanvas surfaceCanvas

   class @MaterialId extends @DataInputComponent
     @register 'Artificial.Reality.Pages.Chemistry.Materials.Scattering.MaterialId'
@@ -80,6 +115,11 @@

       _.sortBy options, 'name'

+    save: ->
+      super arguments...
+
+      @parentComponent.$('select').blur()
+
   class @SurfaceImageUrl extends @DataInputComponent
     @register 'Artificial.Reality.Pages.Chemistry.Materials.Scattering.SurfaceImageUrl'


Remove async require

Remove async require

--- src/lib/authentication.coffee
+++ src/lib/authentication.coffee
@@ -1,5 +1,4 @@
 https = require 'https'
-async = require 'async'
 gui = global.window.nwDispatcher.requireNwGui()
 path = require 'path'
 Datastore = require 'nedb'


change email regex used in affiliations front-end

Make it consistent with back-end regex

--- services/web/public/coffee/main/affiliations/controllers/UserAffiliationsController.coffee
+++ services/web/public/coffee/main/affiliations/controllers/UserAffiliationsController.coffee
@@ -5,7 +5,7 @@
 		$scope.userEmails = []

 		LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
-		EMAIL_REGEX = /^([A-Za-z0-9_\-\.]+)@([^\.]+)\.([A-Za-z0-9_\-\.]+)([^\.])$/
+		EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

 		_matchLocalAndDomain = (userEmailInput) ->
 			match = userEmailInput?.match LOCAL_AND_DOMAIN_REGEX


Adjust plotLine to be alike cash balance

--- src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee
+++ src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee
@@ -30,15 +30,17 @@
         verticalAlign: 'middle'
       xAxis:
         startOnTick: false
+        minPadding: 0
+        tickInterval: 1
         labels:
           formatter: ->
             $filter('mnoDate')(w.content.chart.labels[this.value], getPeriod())
         plotLines: [{
-          color: '#9dbee3'
+          color: 'rgba(0, 85, 255, 0.2)'
           value: getTodayMarker()
-          width: 1.5
+          width: 1
           label:
-            text: 'Today'
+            text: null
             verticalAlign: 'top'
             textAlign: 'center'
             rotation: 0


collections

collections

--- js/todos.coffee
+++ js/todos.coffee
@@ -28,25 +28,29 @@
 		@model.clickPlusOne()
 	initialize: ->
 		@model.on "change", @render, @
-todos = [
-	{
-		id: 1
-		title: "todo item"
-		content: "kajdlkasjdlkasj askdjalkjdla ksajdlkasjdaljd ..."
-		clicks: 0
-	},
-	{
-		id: 2
-		title: "todo item 2"
-		content: "kajdlkasjdlkasj askdjalkjdla ksajdlkasjdaljd ..."
-		clicks: 0
-	}
-]
+#Collection
+TodoList = Backbone.Collection.extend
+	model: TodoModel
+#Collection View
+TodoListView = Backbone.View.extend
+	render: ->
+		@collection.forEach @addItem, @
+	addItem: (item)->
+		todoView = new TodoView
+			model: item
+		todoView.render()
+		html = todoView.el
+		@.$el.append html

+# INIT
+todoList = new TodoList
 todo1 = new TodoModel
-view1 = new TodoView
-	model: todo1
-view1.render()
+todo2 = new TodoModel
+todoList.add todo1
+todoList.add todo2
+todoListView = new TodoListView
+	collection: todoList
+todoListView.render()
 # DOM READY
 $(document).ready ->
-	$("#container").html view1.el+	$("#container").html todoListView.el


[✔] Fixed scoping (“referencesies”).

[✔] Fixed scoping (“referencesies”).

--- vendor/assets/javascripts/jquery/plugins/referencesify/referencesies.js.coffee
+++ vendor/assets/javascripts/jquery/plugins/referencesify/referencesies.js.coffee
@@ -117,6 +117,6 @@

     references = @references.find '.reference'

-    @references.on 'click', @onReferenceClick
+    references.on 'click', @onReferenceClick

     return


New payload structure for autocomplete

New payload structure for autocomplete

--- client/app.coffee
+++ client/app.coffee
@@ -12,8 +12,7 @@
       {host: config.elasticSearch.server.host}
   )
   suggestionCompleter.on('submit', (completion) ->
-    point = JSON.parse(completion.payload.point)
-    map.setPosition(new L.LatLng(point.coordinates[1], point.coordinates[0]))
+    map.setPosition(new L.LatLng(completion.payload.lat, completion.payload.lng))
   )

   hashToProjection = (hash) ->