fix for browsers that cant do alpha

fix for browsers that cant do alpha

--- demo/scripts/advanced.coffee
+++ demo/scripts/advanced.coffee
@@ -1,9 +1,18 @@
+supportsRGBA = true
+scriptElem = document.getElementsByTagName('script')[0]
+try
+  scriptElem.style.color = 'rgba(128,128,128,0.5)'
+catch e
+  supportsRGBA = false
+finally
+  scriptElem.style.color = ""
+
 getColor = (id, lighten) ->
   alpha = if lighten then '.4' else '1.0'
   if id == 'editor-1'
-    return "rgba(0, 153, 255, #{alpha})"
+    return if supportsRGBA then "rgba(0,153,255,#{alpha})" else "rgb(0,153,255)"
   else
-    return "rgba(255, 153, 51, #{alpha})"
+    return if supportsRGBA then "rgba(255,153,51,#{alpha})" else "rgb(255,153,51)"

 initAttribution = ($container, editor) ->
   attribution = new Scribe.Attribution(editor, editor.id, getColor(editor.id, true))


Rename sharejserror to syncerror

Rename sharejserror to syncerror

--- src/sharedcollection.coffee
+++ src/sharedcollection.coffee
@@ -51,7 +51,7 @@
   captureError: (model, method) => (err) =>
     if err
       log "Sync error!", err
-      @trigger "sharejserror", err, model
+      @trigger "syncerror", model, method, err

   create: (model, options={}) ->


postgres: adding users to projects -- VERY NICELY merging in JSONP objects!

--- src/smc-hub/postgres.coffee
+++ src/smc-hub/postgres.coffee
@@ -109,21 +109,27 @@
         opts  = defaults opts,
             query     : required
             params    : []
-            where     : undefined # Used for SELECT: If given, must be a map with keys clauses with $::TYPE  (not $1::TYPE!)
-                                  # and values the corresponding params.  Also, WHERE must not be in the query already.
-                                  # If where[cond] is undefined, then cond is completely **ignored**.
-            values    : undefined # Used for INSERT: If given, then params and where must not be given.   Values is a map
-                                  # {'field1::type1':value, , 'field2::type2':value2, ...} which gets converted to
-                                  # ' (field1, field2, ...) VALUES ($1::type1, $2::type2, ...) '
-                                  # with corresponding params set.  Undefined valued fields are ignored and types may be omited.
-            conflict  : undefined # If given, then values must also be given; appends this to query:
-                                  #     ON CONFLICT (name) DO UPDATE SET value=EXCLUDED.value'
-            jsonb_set : undefined # Used for setting a field that contains a JSONB javascript map.
-                                  # Give as input an object {key1:val1, key2:val2, ...}.
-                                  # Every key has the corresponding value set, unless val is undefined/null, in which
-                                  # case that key is deleted from the JSONB object.  Simple as that!  This is much, much
-                                  # cleaner to use than the generated SQL.   Also, if the field itself is NULL, it gets
-                                  # created automatically.
+            where     : undefined    # Used for SELECT: If given, must be a map with keys clauses with $::TYPE  (not $1::TYPE!)
+                                     # and values the corresponding params.  Also, WHERE must not be in the query already.
+                                     # If where[cond] is undefined, then cond is completely **ignored**.
+            values    : undefined    # Used for INSERT: If given, then params and where must not be given.   Values is a map
+                                     # {'field1::type1':value, , 'field2::type2':value2, ...} which gets converted to
+                                     # ' (field1, field2, ...) VALUES ($1::type1, $2::type2, ...) '
+                                     # with corresponding params set.  Undefined valued fields are ignored and types may be omited.
+            conflict  : undefined    # If given, then values must also be given; appends this to query:
+                                     #     ON CONFLICT (name) DO UPDATE SET value=EXCLUDED.value'
+            jsonb_set : undefined    # Used for setting a field that contains a JSONB javascript map.
+                                     # Give as input an object
+                                     #
+                                     # { field1:{key1:val1, key2:val2, ...}, field2:{key3:val3,...}, ...}
+                                     #
+                                     # In each field, every key has the corresponding value set, unless val is undefined/null, in which
+                                     # case that key is deleted from the JSONB object fieldi.  Simple as that!  This is much, much
+                                     # cleaner to use than SQL.   Also, if the value in fieldi itself is NULL, it gets
+                                     # created automatically.
+            jsonb_merge : undefined  # Exactly lke jsonb_set, but when val1 (say) is an object, it merges that object in,
+                                     # *instead of* setting field1[key1]=val1.  So after this field1[key1] has what was in it
+                                     # and also what is in val1.  Obviously field1[key1] had better have been an array or NULL.
             cb     : required
         dbg = @_dbg("_query('#{opts.query}') (concurrent=#{@_concurrent_queries})")
         dbg()
@@ -132,25 +138,40 @@
             return

         push_param = (param, type) ->
-            if type.toUpperCase() == 'JSONB' and misc.is_array(param)
+            if type?.toUpperCase() == 'JSONB'
                 param = misc.to_json(param)  # I don't understand why this is needed by the driver....
             opts.params.push(param)
             return opts.params.length

+        if opts.jsonb_merge?
+            if opts.jsonb_set?
+                opts.cb("if jsonb_merge is set then jsonb_set must not be set")
+                return
+            opts.jsonb_set = opts.jsonb_merge
+
         if opts.jsonb_set?
-            opts.query += " SET "
-            v = []
-            for field, data of opts.jsonb_set
-                obj = "COALESCE(#{field}, '{}'::JSONB)"
+            # This little piece of very hard to write (and clever?) code
+            # makes it so we can set or **merge in at any nested level (!)
+            # arbitrary JSON objects.  We can also delete any key at any
+            # level by making the value null or undefined!  This is amazingly
+            # easy to use in queries -- basically making JSONP with postgres
+            # as expressive as RethinkDB REQL (even better in some ways).
+            set = (field, data, path) =>
+                obj = "COALESCE(#{field}#>'{#{path.join(',')}}', '{}'::JSONB)"
                 for key, val of data
                     if not val?
                         # remove key from object
                         obj = "(#{obj} - '#{key}')"
                     else
-                        # add key to object
-                        obj = "JSONB_SET(#{obj}, '{#{key}}', $#{push_param(val, 'JSONB')}::JSONB)"
-                v.push(" #{field} = #{obj}")
-            opts.query += v.join(' , ')
+                        if opts.jsonb_merge? and typeof(val) == 'object'
+                            subobj = set(field, val, path.concat([key]))
+                            obj    = "JSONB_SET(#{obj}, '{#{key}}', #{subobj})"
+                        else
+                            # completely replace field[key] with val.
+                            obj = "JSONB_SET(#{obj}, '{#{key}}', $#{push_param(val, 'JSONB')}::JSONB)"
+                return obj
+            v = ("#{field}=#{set(field, data, [])}" for field, data of opts.jsonb_set)
+            opts.query += " SET " + v.join(' , ')

         if opts.values?
             if opts.where?
@@ -871,7 +892,38 @@
             cb     : opts.cb

     do_account_creation_actions: (opts) =>
-        throw Error("NotImplementedError")
+        opts = defaults opts,
+            email_address : required
+            account_id    : required
+            cb            : required
+        dbg = @_dbg("do_account_creation_actions(email_address='#{opts.email_address}')")
+        @account_creation_actions
+            email_address : opts.email_address
+            cb            : (err, actions) =>
+                if err
+                    opts.cb(err); return
+                f = (action, cb) =>
+                    dbg("account_creation_actions: action = #{misc.to_json(action)}")
+                    if action.action == 'add_to_project'
+                        @add_user_to_project
+                            project_id : action.project_id
+                            account_id : opts.account_id
+                            group      : action.group
+                            cb         : (err) =>
+                                if err
+                                    dbg("Error adding user to project: #{err}")
+                                cb(err)
+                    else
+                        # TODO: need to report this some better way, maybe email?
+                        dbg("skipping unknown action -- #{action.action}")
+                        cb()
+                async.map actions, f, (err) =>
+                    if not err
+                        @account_creation_actions_success
+                            account_id : opts.account_id
+                            cb         : opts.cb
+                    else
+                        opts.cb(err)

     set_stripe_customer_id: (opts) =>
         throw Error("NotImplementedError")
@@ -1001,7 +1053,25 @@
         throw Error("NotImplementedError")

     create_project: (opts) =>
-        throw Error("NotImplementedError")
+        opts = defaults opts,
+            account_id  : required    # initial owner
+            title       : undefined
+            description : undefined
+            cb          : required    # cb(err, project_id)
+        if not @_validate_opts(opts) then return
+        project_id = misc.uuid()
+        now = new Date()
+        @_query
+            query  : "INSERT INTO projects"
+            values :
+                project_id  : project_id
+                title       : opts.title
+                description : opts.description
+                created     : now
+                last_edited : now
+                users       : {"#{opts.account_id}":{group:'owner'}}
+            cb : (err, result) =>
+                opts.cb(err, if not err then project_id)

     get_project: (opts) =>
         throw Error("NotImplementedError")
@@ -1013,7 +1083,19 @@
         throw Error("NotImplementedError")

     add_user_to_project: (opts) =>
-        throw Error("NotImplementedError")
+        opts = defaults opts,
+            project_id : required
+            account_id : required
+            group      : 'collaborator'  # see misc.PROJECT_GROUPS above
+            cb         : required  # cb(err)
+        if not @_validate_opts(opts) then return
+        @_query
+            query       : 'UPDATE projects'
+            jsonb_merge :
+                users   :
+                    "#{opts.account_id}":
+                        group: opts.group
+            cb          : opts.cb

     remove_collaborator_from_project: (opts) =>
         throw Error("NotImplementedError")


fix yet another stupid tasks bug (on ipad...)

--- src/smc-webapp/tasks.coffee
+++ src/smc-webapp/tasks.coffee
@@ -939,8 +939,11 @@
         e = task.element
         if not e?
             return
-        if e.hasClass('webapp-task-editing-desc') and e.data('cm')?
-            e.data('cm').focus()
+        cm = e.data('cm')
+        if e.hasClass('webapp-task-editing-desc') and cm?
+            cm.focus()
+            cm.refresh()
+            currently_focused_editor = cm
             return
         e.find(".webapp-task-desc").addClass('webapp-task-desc-editing')
         e.find(".webapp-task-toggle-icons").hide()
@@ -951,6 +954,7 @@
         if elt.length > 0
             elt.show()
             cm = e.data('cm')
+            currently_focused_editor = cm
             cm.focus()
             e.addClass('webapp-task-editing-desc')
             # apply any changes
@@ -962,6 +966,8 @@

         finished = false
         stop_editing = () =>
+            if currently_focused_editor?.task_id == task.task_id
+                currently_focused_editor = undefined
             finished = true
             e.removeClass('webapp-task-editing-desc')
             e.find(".webapp-task-desc").removeClass('webapp-task-desc-editing')
@@ -1008,6 +1014,7 @@

         cm = CodeMirror.fromTextArea(elt.find("textarea")[0], opts)
         cm.save = @save
+        cm.task_id = task.task_id
         if editor_settings.bindings == 'vim'
             cm.setOption("vimMode", true)

@@ -1047,13 +1054,17 @@
         # (unless there are incoming sync updates to process).
         cm.on 'changes', underscore.debounce(sync_desc, 2000)

+        # NOTE: calling cm.focus() does NOT cause this 'focus' below to get called always, at least
+        # not on ipad.  So... that's why we explicitly set currently_focused_editor in various
+        # places above too.
         cm.on 'focus', () ->
             currently_focused_editor = cm
             $(cm.getWrapperElement()).addClass('webapp-new-task-cm-editor-focus')

         cm.on 'blur', () ->
             $(cm.getWrapperElement()).removeClass('webapp-new-task-cm-editor-focus')
-            currently_focused_editor = undefined
+            if currently_focused_editor?.task_id == task.task_id
+                currently_focused_editor = undefined

         cm.focus()
         if cursor_at_end


Replace focusout event handlers on hiddenInput shim with blur handlers

The focusout event doesn’t seem to work for elements in the shadow DOM.
Other people seem to share this experience:

https://code.google.com/p/chromium/issues/detail?id=378163#c7

--- src/text-editor-view.coffee
+++ src/text-editor-view.coffee
@@ -80,6 +80,10 @@
     @overlayer = $("

").appendTo(this)
     @hiddenInput = @root.find('.hidden-input')

+    @hiddenInput.on = (args...) =>
+      args[0] = 'blur' if args[0] is 'focusout'
+      $::on.apply(this, args)
+
     @subscribe atom.config.observe 'editor.showLineNumbers', =>
       @gutter = @root.find('.gutter')


Add Trix.Object#toUTF16String

Add Trix.Object#toUTF16String

--- src/trix/core/object.coffee
+++ src/trix/core/object.coffee
@@ -24,7 +24,10 @@
   contentsForInspection: ->

   toJSONString: ->
-    JSON.stringify this
+    JSON.stringify(this)
+
+  toUTF16String: ->
+    Trix.UTF16String.box(this)

   getCacheKey: ->
     @id.toString()


Allow setting title from props

Allow setting title from props

Also move setState to below JSON.parse because react-dev-tools also uses
sendmessage

--- src/user/login-gateway.cjsx
+++ src/user/login-gateway.cjsx
@@ -6,6 +6,12 @@
 SECOND = 1000

 LoginGateway = React.createClass
+
+  propTypes:
+    title: React.PropTypes.string
+
+  getDefaultProps: ->
+    title: 'You need to login or signup in order to use ConceptCoach™'

   getInitialState: ->
     loginWindow: false
@@ -28,10 +34,10 @@
   parseAndDispatchMessage: (msg) ->
     return unless @isMounted()
     try
-      @setState(loginWindow: false) # cancel checking for close
       data = JSON.parse(msg.data)
       if data.user
         api.channel.emit 'user.status.receive.fetch', data: data
+      @setState(loginWindow: false) # cancel checking for close
     catch error
       console.warn(error)
   componentWillUnmount: ->
@@ -65,9 +71,8 @@

   render: ->
-

-
You need to login or signup in order to use ConceptCoach™

+
{@props.title}

       {if @state.loginWindow then @renderWaiting() else @renderLogin()}


Fix bug where sometimes latest widget displayed more posts than there should be

--- client/views/widget/latest.coffee
+++ client/views/widget/latest.coffee
@@ -7,7 +7,8 @@

 Template.blogLatest.helpers
   latest: ->
-    Blog.Post.all()
+    num = if @num then @num else 3
+    Blog.Post.all limit: num

   date: (date) ->
     if date


Fix bad classname in js

Fix bad classname in js


Ensure that we get the last  for a key.

Ensure that we get the last  for a key.

(Because we're scanning backwards throught the mappings, we shouldn't
overwrite an existing mapping.)

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -39,7 +39,7 @@
           if tokens.length == 3
             fromChar = @parseKeySequence tokens[1]
             toChar = @parseKeySequence tokens[2]
-            @mapKeyRegistry[fromChar[0]] = toChar[0] if fromChar.length == toChar.length == 1
+            @mapKeyRegistry[fromChar[0]] ?= toChar[0] if fromChar.length == toChar.length == 1

     chrome.storage.local.set mapKeyRegistry: @mapKeyRegistry
     @installKeyStateMapping()


Better wording fo command description.

Better wording fo command description.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -341,7 +341,7 @@
   openCopiedUrlInNewTab: ["Open the clipboard's URL in a new tab", { background: true, repeatLimit: 20 }]

   enterInsertMode: ["Enter insert mode", { noRepeat: true }]
-  passNextKey: ["Pass the next key to Chrome"]
+  passNextKey: ["Pass the next key to the page"]
   enterVisualMode: ["Enter visual mode", { noRepeat: true }]
   enterVisualLineMode: ["Enter visual line mode", { noRepeat: true }]


option to specify server port from the command line

--- tools/server.coffee
+++ tools/server.coffee
@@ -20,7 +20,7 @@

 class DoppioServer
   constructor: ->
-    @port = 8000
+    @port = argv.port or 8000

     @Mode =
       DEV: 0


indention fix

indention fix

--- coffeescript/feedlyconsole.coffee
+++ coffeescript/feedlyconsole.coffee
@@ -64,7 +64,7 @@

     # **templates.not_found**
     # Override of the pathhandler *not_found* template, since we will throw *not_found* if you try to access a valid file. This is done for the simplicity of the tutorial.
-     _self.shell.templates.not_found = _.template("
<%=cmd%>: <%=path%>: No such directory
")
+    _self.shell.templates.not_found = _.template("
<%=cmd%>: <%=path%>: No such directory
")

     #**templates.rateLimitTemplate**
     # rate limiting will be added later to feedly api


update temporary covid year validation

update temporary covid year validation

see 4b397d619dc9f57632d25a2042db23beb6b04a1f for reference

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -444,9 +444,9 @@
           @appendMessage(qParent, "Not a valid date")
           @addErrorClass(question)
         # temporary condition
-        else if parseInt(year) > 2021 || parseInt(year) < 2012
-          @logThis(question, "validateDateByYears", "the year must be from 2012 to 2021")
-          @appendMessage(qParent, "the year must be from 2012 to 2021")
+        else if parseInt(year) > 2022 || parseInt(year) < 2014
+          @logThis(question, "validateDateByYears", "the year must be from 2014 to 2022")
+          @appendMessage(qParent, "the year must be from 2014 to 2022")
           @addErrorClass(question)

   validateInnovationFinancialDate: (question) ->


[Form] Frontend validation fix

[Form] Frontend validation fix

[TRELLO STORY](https://trello.com/c/CWdHBvL3/1177-qae16imp-application-issue-iqbal-wahhab-roast-restaurant)

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -275,7 +275,7 @@

         value = subq.val().toString()

-        if not value.match(@numberRegex)
+        if not $.trim(value).match(@numberRegex)
           @log_this(question, "validateMoneyByYears", "Not a valid currency value")
           @appendMessage(err_container, "Not a valid currency value")
           @addErrorClass(question)


returning a promise

returning a promise

--- app/assets/javascripts/uploadcare/widget/dialog.js.coffee
+++ app/assets/javascripts/uploadcare/widget/dialog.js.coffee
@@ -26,6 +26,7 @@
         @_createDialog()

         @always @_closeDialog
+       .promise()

     dialogUiMixin =


localoutput could null

localoutput could null


Added "selection based" optioon for octave

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -495,6 +495,9 @@
       args: (context) -> [context.filepath]

   Octave:
+    "Selection Based":
+      command: "octave"
+      args: (context) -> ['-p', context.filepath.replace(/[^\/]*$/, ''), '--eval', context.getCode()]
     "File Based":
       command: "octave"
       args: (context) -> ['-p', context.filepath.replace(/[^\/]*$/, ''), context.filepath]


Clear cookie and token if we receive errors

--- components/GetPortalStates.coffee
+++ components/GetPortalStates.coffee
@@ -23,6 +23,8 @@
     auth: auth
   options = getRequestOptions 'GET', apiHost, cookiePath
   req = https.request options, (res) ->
+    unless res.headers['set-cookie']
+      return callback new Error 'No cookie received'
     callback null, res.headers['set-cookie']
   req.setTimeout 2000
   req.end()
@@ -125,18 +127,25 @@
         return callback err if err
         c.cookie = cookie
         obtainXsrf c.cookie, (err, xsrf) ->
-          return callback err if err
+          if err
+            c.cookie = null
+            return callback err
           c.xsrf = xsrf.xsrf
           c.xsrfValid = xsrf.timeout
           getPortals guids, c.params.username, c.cookie, c.xsrf, (err, states) ->
-            return callback err if err
+            if err
+              c.cookie = null
+              c.xsrf = null
+              return callback err
             out.send states
             do callback
       return

     unless hasXsrf c
       obtainXsrf c.cookie, (err, xsrf) ->
-        return callback err if err
+        if err
+          c.cookie = null
+          return callback err
         c.xsrf = xsrf.xsrf
         c.xsrfValid = xsrf.timeout
         getPortals guids, c.params.username, c.cookie, c.xsrf, (err, states) ->


Define current top-level graph as a 'main'

--- components/OpenProjectMode.coffee
+++ components/OpenProjectMode.coffee
@@ -1,9 +1,12 @@
 noflo = require 'noflo'
 { getGraphType, getComponentType, getRemoteNodes } = require '../src/runtime'

-sendGraphs = (client, graphs) ->
+sendGraphs = (client, graphs, currentGraphs) ->
   compatible = graphs.filter (g) -> getGraphType(g) is client.definition.type
-  Promise.all(compatible.map((g) -> client.protocol.graph.send(g)))
+  Promise.all(compatible.map((g) ->
+    main = if g is currentGraphs[0] then true else false
+    client.protocol.graph.send(g, main)
+  ))

 sendComponents = (client, components, namespace) ->
   compatible = components.filter (c) -> getComponentType(c) is client.definition.type
@@ -45,7 +48,7 @@
         sendComponents client, route.project.components, route.project.namespace
       )
       .then(() ->
-        sendGraphs client, route.project.graphs
+        sendGraphs client, route.project.graphs, route.graphs
       )
       .then(() ->
         getRemoteNodes client, route


Re-introduce ctrl, shift keys as link hint mode modifiers

This fixes #1096.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -262,13 +262,25 @@
       if event.keyCode == keyCodes.shiftKey
         @setOpenLinkMode(if @mode is OPEN_IN_CURRENT_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_CURRENT_TAB)

+        handlerStack.push
+          keyup: (event) =>
+            return if (event.keyCode != keyCodes.shiftKey)
+            @setOpenLinkMode prev_mode if @isActive
+            handlerStack.remove()
+
       else # event.keyCode == keyCodes.ctrlKey
         @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB)
+
+        handlerStack.push
+          keyup: (event) =>
+            return if (event.keyCode != keyCodes.ctrlKey)
+            @setOpenLinkMode prev_mode if @isActive
+            handlerStack.remove()

     # TODO(philc): Ignore keys that have modifiers.
     if (KeyboardUtils.isEscape(event))
       @deactivateMode()
-    else
+    else if (event.keyCode != keyCodes.shiftKey and event.keyCode != keyCodes.ctrlKey)
       keyResult = @getMarkerMatcher().matchHintsByKey(hintMarkers, event)
       linksMatched = keyResult.linksMatched
       delay = keyResult.delay ? 0


#342 :bug: fix for pre-targeted child e.g TransformWordBySelectList

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -477,7 +477,8 @@
   initialize: ->
     @vimState.onDidConfirmSelectList (transformer) =>
       @vimState.reset()
-      @vimState.operationStack.run(transformer.name)
+      target = @target?.constructor.name
+      @vimState.operationStack.run(transformer.name, {target})
     @focusSelectList({items: @getItems()})

   execute: ->


:gift: preselect changed are for ChanageSurroundAnyPair

need to refactor further, this approach could be applied to other
TextObject. for better UI feedback.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -85,7 +85,10 @@
     @vimState.flasher.flash(options, fn)

   eachSelection: (fn) ->
-    return unless @target.select()
+    if @selected?
+      return unless @selected
+    else
+      return unless @target.select()
     @editor.transact =>
       for s in @editor.getSelections()
         if @flashTarget and settings.get('flashOnOperate')
@@ -134,13 +137,18 @@

   # [FIXME] duplicate to Yank, need to consolidate as like adjustCursor().
   execute: ->
-    if @target.isLinewise?() or settings.get('stayOnTransformString')
+    if @points?
+      points = @points
+    else if @target.isLinewise?() or settings.get('stayOnTransformString')
       points = _.pluck(@editor.getSelectedBufferRanges(), 'start')
     @eachSelection (s) =>
       range = s.insertText @getNewText(s.getText())
       if @adjustCursor
         s.cursor.setBufferPosition(points?.shift() ? range.start)
     @vimState.activate('normal')
+    # FIXME: to more cleaner approach to preserve/restore Points.
+    @selected = null
+    @points = null

 class ToggleCase extends TransformString
   @extend()
@@ -276,6 +284,15 @@
   constructor: ->
     super
     @compose @new("AnyPair", inclusive: true)
+    @preSelect()
+
+  # FIXME very inperative implementation. find more generic and consistent approach.
+  # like preservePoints() and restorePoints().
+  preSelect: ->
+    if @target.isLinewise?() or settings.get('stayOnTransformString')
+      @points = _.pluck(@editor.getSelectedBufferRanges(), 'start')
+    if @selected = @target.select()
+      @vimState.hover.add(@editor.getSelectedText()[0])

   onConfirm: (@char) ->
     @input = @char


Initialise project before opening the palette view for fresh new project

--- lib/pigments.coffee
+++ lib/pigments.coffee
@@ -110,9 +110,10 @@
     atom.workspace.openURIInPane(uri, pane, {})

   showPalette: ->
-    uri = "pigments://palette"
+    @project.initialize().then ->
+      uri = "pigments://palette"

-    pane = atom.workspace.paneForURI(uri)
-    pane ||= atom.workspace.getActivePane()
+      pane = atom.workspace.paneForURI(uri)
+      pane ||= atom.workspace.getActivePane()

-    atom.workspace.openURIInPane(uri, pane, {})
+      atom.workspace.openURIInPane(uri, pane, {})


Atlas of Worlds update

Atlas of Worlds update

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -149,6 +149,17 @@
     { snippet: '"Portal Shredder"' },
     { snippet: '"Rod of Detonation"' },
     { snippet: '"Sand of Eternity"' },
+    #Atlas of Worlds
+    { snippet: '"Shaper\'s Orb (Tier 1)"' },
+    { snippet: '"Shaper\'s Orb (Tier 2)"' },
+    { snippet: '"Shaper\'s Orb (Tier 3)"' },
+    { snippet: '"Shaper\'s Orb (Tier 4)"' },
+    { snippet: '"Shaper\'s Orb (Tier 5)"' },
+    { snippet: '"Shaper\'s Orb (Tier 6)"' },
+    { snippet: '"Shaper\'s Orb (Tier 7)"' },
+    { snippet: '"Shaper\'s Orb (Tier 8)"' },
+    { snippet: '"Shaper\'s Orb (Tier 9)"' },
+    { snippet: '"Shaper\'s Orb (Tier 10)"' },
   ]

   amulets: [
@@ -276,7 +287,127 @@
     { snippet: '"Cartographer\'s Seal"' },
     { snippet: '"Shaper\'s Orb"' },
     #Essence
+    { snippet: '"Whispering Essence of Hatred"' },
+    { snippet: '"Muttering Essence of Hatred"' },
+    { snippet: '"Weeping Essence of Hatred"' },
+    { snippet: '"Wailing Essence of Hatred"' },
+    { snippet: '"Screaming Essence of Hatred"' },
+    { snippet: '"Shrieking Essence of Hatred"' },
+    { snippet: '"Deafening Essence of Hatred"' },
+    { snippet: '"Whispering Essence of Woe"' },
+    { snippet: '"Muttering Essence of Woe"' },
+    { snippet: '"Weeping Essence of Woe"' },
+    { snippet: '"Wailing Essence of Woe"' },
+    { snippet: '"Screaming Essence of Woe"' },
+    { snippet: '"Shrieking Essence of Woe"' },
+    { snippet: '"Deafening Essence of Woe"' },
+    { snippet: '"Whispering Essence of Greed"' },
+    { snippet: '"Muttering Essence of Greed"' },
+    { snippet: '"Weeping Essence of Greed"' },
+    { snippet: '"Wailing Essence of Greed"' },
+    { snippet: '"Screaming Essence of Greed"' },
+    { snippet: '"Shrieking Essence of Greed"' },
+    { snippet: '"Deafening Essence of Greed"' },
+    { snippet: '"Whispering Essence of Contempt"' },
+    { snippet: '"Muttering Essence of Contempt"' },
+    { snippet: '"Weeping Essence of Contempt"' },
+    { snippet: '"Wailing Essence of Contempt"' },
+    { snippet: '"Screaming Essence of Contempt"' },
+    { snippet: '"Shrieking Essence of Contempt"' },
+    { snippet: '"Deafening Essence of Contempt"' },
+    { snippet: '"Muttering Essence of Sorrow"' },
+    { snippet: '"Weeping Essence of Sorrow"' },
+    { snippet: '"Wailing Essence of Sorrow"' },
+    { snippet: '"Screaming Essence of Sorrow"' },
+    { snippet: '"Shrieking Essence of Sorrow"' },
+    { snippet: '"Deafening Essence of Sorrow"' },
+    { snippet: '"Muttering Essence of Anger"' },
+    { snippet: '"Weeping Essence of Anger"' },
+    { snippet: '"Wailing Essence of Anger"' },
+    { snippet: '"Screaming Essence of Anger"' },
+    { snippet: '"Shrieking Essence of Anger"' },
+    { snippet: '"Deafening Essence of Anger"' },
+    { snippet: '"Muttering Essence of Torment"' },
+    { snippet: '"Weeping Essence of Torment"' },
+    { snippet: '"Wailing Essence of Torment"' },
+    { snippet: '"Screaming Essence of Torment"' },
+    { snippet: '"Shrieking Essence of Torment"' },
+    { snippet: '"Deafening Essence of Torment"' },
+    { snippet: '"Muttering Essence of Fear"' },
+    { snippet: '"Weeping Essence of Fear"' },
+    { snippet: '"Wailing Essence of Fear"' },
+    { snippet: '"Screaming Essence of Fear"' },
+    { snippet: '"Shrieking Essence of Fear"' },
+    { snippet: '"Deafening Essence of Fear"' },
+    { snippet: '"Weeping Essence of Suffering"' },
+    { snippet: '"Wailing Essence of Suffering"' },
+    { snippet: '"Screaming Essence of Suffering"' },
+    { snippet: '"Shrieking Essence of Suffering"' },
+    { snippet: '"Deafening Essence of Suffering"' },
+    { snippet: '"Weeping Essence of Rage"' },
+    { snippet: '"Wailing Essence of Rage"' },
+    { snippet: '"Screaming Essence of Rage"' },
+    { snippet: '"Shrieking Essence of Rage"' },
+    { snippet: '"Deafening Essence of Rage"' },
+    { snippet: '"Weeping Essence of Wrath"' },
+    { snippet: '"Wailing Essence of Wrath"' },
+    { snippet: '"Screaming Essence of Wrath"' },
+    { snippet: '"Shrieking Essence of Wrath"' },
+    { snippet: '"Deafening Essence of Wrath"' },
+    { snippet: '"Weeping Essence of Doubt"' },
+    { snippet: '"Wailing Essence of Doubt"' },
+    { snippet: '"Screaming Essence of Doubt"' },
+    { snippet: '"Shrieking Essence of Doubt"' },
+    { snippet: '"Deafening Essence of Doubt"' },
+    { snippet: '"Wailing Essence of Anguish"' },
+    { snippet: '"Screaming Essence of Anguish"' },
+    { snippet: '"Shrieking Essence of Anguish"' },
+    { snippet: '"Deafening Essence of Anguish"' },
+    { snippet: '"Wailing Essence of Loathing"' },
+    { snippet: '"Screaming Essence of Loathing"' },
+    { snippet: '"Shrieking Essence of Loathing"' },
+    { snippet: '"Deafening Essence of Loathing"' },
+    { snippet: '"Wailing Essence of Spite"' },
+    { snippet: '"Screaming Essence of Spite"' },
+    { snippet: '"Shrieking Essence of Spite"' },
+    { snippet: '"Deafening Essence of Spite"' },
+    { snippet: '"Wailing Essence of Zeal"' },
+    { snippet: '"Screaming Essence of Zeal"' },
+    { snippet: '"Shrieking Essence of Zeal"' },
+    { snippet: '"Deafening Essence of Zeal"' },
+    { snippet: '"Screaming Essence of Misery"' },
+    { snippet: '"Shrieking Essence of Misery"' },
+    { snippet: '"Deafening Essence of Misery"' },
+    { snippet: '"Screaming Essence of Dread"' },
+    { snippet: '"Shrieking Essence of Dread"' },
+    { snippet: '"Deafening Essence of Dread"' },
+    { snippet: '"Screaming Essence of Scorn"' },
+    { snippet: '"Shrieking Essence of Scorn"' },
+    { snippet: '"Deafening Essence of Scorn"' },
+    { snippet: '"Screaming Essence of Envy"' },
+    { snippet: '"Shrieking Essence of Envy"' },
+    { snippet: '"Deafening Essence of Envy"' },
+    { snippet: '"Essence of Hysteria"' },
+    { snippet: '"Essence of Insanity"' },
+    { snippet: '"Essence of Horror"' },
+    { snippet: '"Essence of Delirium"' },
     { snippet: '"Remnant of Corruption"' },
+    { snippet: '"Apprentice Cartographer\'s Sextant"' },
+    { snippet: '"Journeyman Cartographer\'s Sextant"' },
+    { snippet: '"Master Cartographer\'s Sextant"' },
+    { snippet: '"Apprentice Cartographer\'s Seal"' },
+    { snippet: '"Journeyman Cartographer\'s Seal"' },
+    { snippet: '"Master Cartographer\'s Seal"' },
+    { snippet: '"Unshaping Orb"' },
+  ]
+
+  divcards: [
+    #Just a start, I'll add more when I have time
+    #I had no idea these had a base type
+    { snippet: '"The Oath"' },
+    { snippet: '"The Scavenger"' },
+    { snippet: '"The Void"' },
+    { snippet: '"Mawr Blaidd"' },
   ]

   skills: [
@@ -567,7 +698,7 @@

   maps: [
     #Tier 1
-    { snippet: '"Tunnel Map"' },
+    { snippet: '"Crystal Ore Map"' },
     { snippet: '"Jungle Valley Map"' },
     { snippet: '"Arcade Map"' },
     { snippet: '"Desert Map"' },
@@ -576,16 +707,16 @@
     { snippet: '"Beach Map"' },
     { snippet: '"Ghetto Map"' },
     { snippet: '"Oasis Map"' },
-    #{ snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Factory Map"' },

     #Tier 3
     { snippet: '"Channel Map"' },
-    { snippet: '"Abandoned Cavern Map"' },
+    { snippet: '"Cavern Map"' },
     { snippet: '"Vaal Pyramid Map"' },
-    { snippet: '"Sewers Map"' },
+    { snippet: '"Sewer Map"' },
     { snippet: '"Grotto Map"' },
     { snippet: '"Arid Lake Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Marshes Map"' },

     #Tier 4
     { snippet: '"Waste Pool Map"' },
@@ -594,118 +725,188 @@
     { snippet: '"Academy Map"' },
     { snippet: '"Dungeon Map"' },
     { snippet: '"Villa Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Acid Lakes Map"' },

     #Tier 5
     { snippet: '"Pit Map"' },
     { snippet: '"Mesa Map"' },
     { snippet: '"Burial Chamber Map"' },
     { snippet: '"Spider Lair Map"' },
-    { snippet: '"Thicket Map"' },
     { snippet: '"Dunes Map"' },
-    { snippet: '"Dry Peninsula Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Peninsula Map"' },
+    { snippet: '"Primordial Pool Map"' },
+    { snippet: '"Tower Map"' },

     #Tier 6
     { snippet: '"Quarry Map"' },
     { snippet: '"Vaal City Map"' },
     { snippet: '"Canyon Map"' },
-    { snippet: '"Mud Geyser Map"' },
     { snippet: '"Spider Forest Map"' },
     { snippet: '"Ramparts Map"' },
     { snippet: '"Wharf Map"' },
     { snippet: '"Strand Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Thicket Map"' },
+    { snippet: '"Racecourse Map"' },
+    { snippet: '"Shaped Arcade Map"' },
+    { snippet: '"Shaped Crystal Ore Map"' },
+    { snippet: '"Shaped Desert Map"' },
+    { snippet: '"Shaped Jungle Valley Map"' },

     #Tier 7
     { snippet: '"Cells Map"' },
     { snippet: '"Catacombs Map"' },
-    { snippet: '"Colonnade Map"' },
-    { snippet: '"Pier Map"' },
-    { snippet: '"Village Ruin Map"' },
-    { snippet: '"Dry Woods Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Castle Ruins Map"' },
+    { snippet: '"Ashen Woods Map"' },
+    { snippet: '"Arachnid Tomb Map"' },
+    { snippet: '"Armory Map"' },
+    { snippet: '"Mud Geyser Map"' },
+    { snippet: '"Shaped Beach Map"' },
+    { snippet: '"Shaped Ghetto Map"' },
+    { snippet: '"Shaped Oasis Map"' },
+    { snippet: '"Shaped Factory Map"' },

     #Tier 8
     { snippet: '"Tropical Island Map"' },
     { snippet: '"Shore Map"' },
     { snippet: '"Arena Map"' },
-    { snippet: '"Mountain Ledge Map"' },
+    { snippet: '"Atoll Map"' },
     { snippet: '"Bog Map"' },
     { snippet: '"Cemetery Map"' },
     { snippet: '"Arachnid Nest Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Barrows Map"' },
+    { snippet: '"Pier Map"' },
+    { snippet: '"Shaped Channel Map"' },
+    { snippet: '"Shaped Cavern Map"' },
+    { snippet: '"Shaped Vaal Pyramid Map"' },
+    { snippet: '"Shaped Sewer Map"' },
+    { snippet: '"Shaped Grotto Map"' },
+    { snippet: '"Shaped Arid Lake Map"' },
+    { snippet: '"Shaped Marshes Map"' },

     #Tier 9
-    { snippet: '"Reef Map"' },
     { snippet: '"Coves Map"' },
-    { snippet: '"Temple Map"' },
+    { snippet: '"Crypt Map"' },
+    { snippet: '"Museum Map"' },
     { snippet: '"Orchard Map"' },
-    { snippet: '"Crypt Map"' },
     { snippet: '"Overgrown Shrine Map"' },
     { snippet: '"Promenade Map"' },
+    { snippet: '"Reef Map"' },
+    { snippet: '"Temple Map"' },
+    { snippet: '"Shaped Waste Pool Map"' },
+    { snippet: '"Shaped Phantasmagoria Map"' },
+    { snippet: '"Shaped Graveyard Map"' },
+    { snippet: '"Shaped Academy Map"' },
+    { snippet: '"Shaped Dungeon Map"' },
+    { snippet: '"Shaped Villa Map"' },
+    { snippet: '"Shaped Acid Lakes Map"' },

     #Tier 10
+    { snippet: '"Colonnade Map"' },
+    { snippet: '"Arsenal Map"' },
+    { snippet: '"Courtyard Map"' },
+    { snippet: '"Malformation Map"' },
+    { snippet: '"Quay Map"' },
+    { snippet: '"Terrace Map"' },
     { snippet: '"Underground River Map"' },
-    { snippet: '"Malformation Map"' },
-    { snippet: '"Museum Map"' },
-    { snippet: '"Terrace Map"' },
-    { snippet: '"Arsenal Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Shaped Pit Map"' },
+    { snippet: '"Shaped Mesa Map"' },
+    { snippet: '"Shaped Burial Chamber Map"' },
+    { snippet: '"Shaped Spider Lair Map"' },
+    { snippet: '"Shaped Dunes Map"' },
+    { snippet: '"Shaped Peninsula Map"' },
+    { snippet: '"Shaped Primordial Pool Map"' },
+    { snippet: '"Shaped Tower Map"' },

     #Tier 11
+    { snippet: '"Bazaar Map"' },
+    { snippet: '"Chateau Map"' },
+    { snippet: '"Excavation Map"' },
     { snippet: '"Precinct Map"' },
     { snippet: '"Torture Chamber Map"' },
     { snippet: '"Underground Sea Map"' },
-    { snippet: '"Excavation Map"' },
-    { snippet: '"Bazaar Map"' },
-    { snippet: '"Chateau Map"' },
-    { snippet: '"Courtyard Map"' },
+    { snippet: '"Wasteland Map"' },
+    { snippet: '"Shaped Quarry Map"' },
+    { snippet: '"Shaped Vaal City Map"' },
+    { snippet: '"Shaped Canyon Map"' },
+    { snippet: '"Shaped Spider Forest Map"' },
+    { snippet: '"Shaped Ramparts Map"' },
+    { snippet: '"Shaped Wharf Map"' },
+    { snippet: '"Shaped Strand Map"' },
+    { snippet: '"Shaped Thicket Map"' },
+    { snippet: '"Shaped Racecourse Map"' },

     #Tier 12
+    { snippet: '"Crematorium Map"' },
+    { snippet: '"Estuary Map"' },
+    { snippet: '"Necropolis Map"' },
+    { snippet: '"Plateau Map"' },
+    { snippet: '"Ivory Temple Map"' },
     { snippet: '"Residence Map"' },
-    { snippet: '"Plateau Map"' },
-    { snippet: '"Necropolis Map"' },
     { snippet: '"Shipyard Map"' },
-    { snippet: '"Estuary Map"' },
-    { snippet: '"Crematorium Map"' },
-    { snippet: '"Wasteland Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Vault Map"' },
+    { snippet: '"Shaped Cells Map"' },
+    { snippet: '"Shaped Catacombs Map"' },
+    { snippet: '"Shaped Castle Ruins Map"' },
+    { snippet: '"Shaped Ashen Woods Map"' },
+    { snippet: '"Shaped Arachnid Tomb Map"' },
+    { snippet: '"Shaped Armory Map"' },
+    { snippet: '"Shaped Mud Geyser Map"' },

     #Tier 13
-    { snippet: '"Plaza Map"' },
+    { snippet: '"Lair Map"' },
+    { snippet: '"Beacon Map"' },
     { snippet: '"Gorge Map"' },
     { snippet: '"High Gardens Map"' },
+    { snippet: '"Plaza Map"' },
+    { snippet: '"Scriptorium Map"' },
+    { snippet: '"Sulphur Wastes Map"' },
     { snippet: '"Waterways Map"' },
-    { snippet: '"Lighthouse Map"' },
-    { snippet: '"Sulphur Wastes Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Shaped Tropical Island Map"' },
+    { snippet: '"Shaped Shore Map"' },
+    { snippet: '"Shaped Arena Map"' },
+    { snippet: '"Shaped Atoll Map"' },
+    { snippet: '"Shaped Bog Map"' },
+    { snippet: '"Shaped Cemetery Map"' },
+    { snippet: '"Shaped Arachnid Nest Map"' },
+    { snippet: '"Shaped Barrows Map"' },
+    { snippet: '"Shaped Pier Map"' },

     #Tier 14
-    { snippet: '"Conservatory Map"' },
+    { snippet: '"Maze Map"' },
+    { snippet: '"Mineral Pools Map"' },
+    { snippet: '"Palace Map"' },
     { snippet: '"Shrine Map"' },
-    { snippet: '"Palace Map"' },
     { snippet: '"Springs Map"' },
     { snippet: '"Volcano Map"' },
-    # { snippet: '"AoW New Unknown Map"' },
+    { snippet: '"Shaped Coves Map"' },
+    { snippet: '"Shaped Crypt Map"' },
+    { snippet: '"Shaped Museum Map"' },
+    { snippet: '"Shaped Orchard Map"' },
+    { snippet: '"Shaped Overgrown Shrine Map"' },
+    { snippet: '"Shaped Promenade Map"' },
+    { snippet: '"Shaped Reef Map"' },
+    { snippet: '"Shaped Temple Map"' },

     #Tier 15
+    { snippet: '"Abyss Map"' },
     { snippet: '"Colosseum Map"' },
-    { snippet: '"Abyss Map"' },
+    { snippet: '"Core Map"' },
+    { snippet: '"Dark Forest Map"' },
     { snippet: '"Overgrown Ruin Map"' },
-    { snippet: '"Dark Forest Map"' },
-    { snippet: '"Core Map"' },
+    { snippet: '"Shaped Colonnade Map"' },
+    { snippet: '"Shaped Arsenal Map"' },
+    { snippet: '"Shaped Courtyard Map"' },
+    { snippet: '"Shaped Malformation Map"' },
+    { snippet: '"Shaped Quay Map"' },
+    { snippet: '"Shaped Terrace Map"' },
+    { snippet: '"Shaped Underground River Map"' },

     #Tier 16
+    { snippet: '"Pit of the Chimera Map"' },
+    { snippet: '"Lair of the Hydra Map"' },
+    { snippet: '"Maze of the Minotaur Map"' },
+    { snippet: '"Forge of the Phoenix Map"' },
     { snippet: '"Vaal Temple Map"' },
-    { snippet: '"Lair of the Hydra Map"' },
-    # { snippet: '"Minotaur Map"' },
-    # { snippet: '"Chimera Map"' },
-    # { snippet: '"Phoenix Map"' },
   ]

   fragments: [


Retrieve: use datatype when templateId is missing

When cql-execution does a retrieve, it should send the datatype when the templateID is missing (instead of sending undefined).

--- Src/coffeescript/cql-execution/src/elm/external.coffee
+++ Src/coffeescript/cql-execution/src/elm/external.coffee
@@ -12,7 +12,7 @@
     @dateRange = build json.dateRange

   exec: (ctx) ->
-    records = ctx.findRecords(@templateId)
+    records = ctx.findRecords(@templateId ? @datatype)
     if @codes
       valueset = @codes.exec(ctx)
       records = (r for r in records when valueset.hasCode(r.getCode(@codeProperty)))


fixing issue based on dstu comments for multiplcation of quantity and decimal values

--- Src/coffeescript/cql-execution/src/elm/quantity.coffee
+++ Src/coffeescript/cql-execution/src/elm/quantity.coffee
@@ -182,4 +182,4 @@
     a.multiplyBy(b)
   else
     [q,d]  = if a instanceof Quantity then [a,b] else [b,a]
-    q.value * d
+    createQuantity(q.value * d, q.unit)


:lipstick:

:lipstick:

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -21,13 +21,15 @@
   roaster text, options, (error, html) =>
     return callback(error) if error

-    scopeName = atom.syntax.selectGrammar(filePath, text)?.scopeName
-    defaultCodeLanguage = if scopeName is 'source.litcoffee' then 'coffeescript'
-    else 'text'
+    {scopeName} = atom.syntax.selectGrammar(filePath, text)
+    if scopeName is 'source.litcoffee'
+      defaultCodeLanguage = 'coffee'
+    else
+      defaultCodeLanguage = 'text'

     html = sanitize(html)
     html = resolveImagePaths(html, filePath)
-    html = tokenizeCodeBlocks(html,defaultCodeLanguage)
+    html = tokenizeCodeBlocks(html, defaultCodeLanguage)
     callback(null, html.html().trim())

 exports.toText = (text, filePath, callback) ->
@@ -78,7 +80,7 @@

   html

-tokenizeCodeBlocks = (html,defaultLanguage='text') ->
+tokenizeCodeBlocks = (html, defaultLanguage='text') ->
   html = $(html)

   if fontFamily = atom.config.get('editor.fontFamily')


refactor for prepare split mongoose.

refactor for prepare split mongoose.

--- lib/resource.coffee
+++ lib/resource.coffee
@@ -17,7 +17,7 @@

       metadata.add resource, model

-      mongooseModel = mongoose.model(resource, model)
+      mongooseModel = convertToMongoose(resource, model)
       options = _.extend(globalQueryLimit, params.options) || {}
       rest = params.rest || {}
       actions = params.actions || []
@@ -62,6 +62,14 @@
             if checkAuth(action.auth)
               action(req, res, next)

+convertToMongoose = (name, model) ->
+  # todo
+  for name of model
+    field = model[name]
+    console.log field
+  return mongoose.model(name, model)
+
+
 checkAuth = (auth, req, res) ->
   if auth && !auth(req)
     res.send 401


Security: Forbid position:fixed

Security: Forbid position:fixed

--- lib/sanitize.coffee
+++ lib/sanitize.coffee
@@ -91,19 +91,20 @@
 sanitizeHtml.defaults.allowedTags.push 'nobr'

 ## https://stackoverflow.com/questions/9329552/explain-regex-that-finds-css-comments
-cssCommentRegex = "\\/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*\\/"
-cssBlankRegex = "(?:\\s|#{cssCommentRegex})*"
+cssCommentRegex = /\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g

-## We used to always forbid position:absolute/fixed, but KaTeX needs them
-## (see e.g. `\not`).  Now we just ensure the containing block is reasonable.
-#sanitizeHtml.defaults.transformTags =
-#  '*': (tagName, attribs) ->
-#    if attribs.style? and
-#       ///\bposition#{cssBlankRegex}:#{cssBlankRegex}(absolute|fixed)///i.test(
-#         attribs.style)
-#      delete attribs.style
-#    tagName: tagName
-#    attribs: attribs
+## We used to always forbid position:absolute, but KaTeX needs them
+## (see e.g. `\not`).  But we do need to forbid position:fixed, because
+## this can break outside the overflow:auto constraint of the message body.
+sanitizeHtml.defaults.transformTags =
+  '*': (tagName, attribs) ->
+    if attribs.style?
+      ## Remove all CSS comments. Replace them with space to avoid
+      ## accidentally forming a /* from surrounding "/" and "*".
+      attribs.style = attribs.style.replace cssCommentRegex, ' '
+      .replace /\bposition\s*:\s*fixed/ig, ''
+    tagName: tagName
+    attribs: attribs

 jsdiff = require 'diff'


Fix typo

Fix typo

--- lib/searcher.coffee
+++ lib/searcher.coffee
@@ -102,7 +102,7 @@
       command = 'emacs:input-isearch-'+code
       searchKeybindings[searchSelector][if s==' ' then 'space' else s] = command
       isearchCommandMap[command] = ((s_)=>(=> @incrementSearch s_))(s)
-    @isearchKeymaps = atom.keymaps.add 'emacs-iserch-keymap', searchKeybindings, 0
+    @isearchKeymaps = atom.keymaps.add 'emacs-isearch-keymap', searchKeybindings, 0
     @isearchCommands = atom.commands.add 'atom-text-editor', isearchCommandMap

   search: (e, forward, useRegex) ->


Use Config::set and ::unset

Use Config::set and ::unset

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -94,7 +94,7 @@
         userSnippetsFile = new File(userSnippetsPath)
         userSnippetsFile.on 'moved removed contents-changed', =>
           atom.config.transact =>
-            atom.config.scopedSettingsStore.removePropertiesForSource(userSnippetsPath)
+            atom.config.unset(null, source: userSnippetsPath)
             @loadSnippetsFile userSnippetsPath, (result) =>
               @add(userSnippetsPath, result)
         callback(new Disposable -> userSnippetsFile.off())
@@ -142,7 +142,6 @@
       callback(object)

   add: (filePath, snippetsBySelector) ->
-    disposable = new CompositeDisposable
     for selector, snippetsByName of snippetsBySelector
       snippetsByPrefix = {}
       for name, attributes of snippetsByName
@@ -153,8 +152,7 @@
         bodyTree ?= @getBodyParser().parse(body)
         snippet = new Snippet({name, prefix, bodyTree, bodyText: body})
         snippetsByPrefix[snippet.prefix] = snippet
-      disposable.add atom.config.addScopedSettings(filePath, selector, snippets: snippetsByPrefix)
-    disposable
+      atom.config.set('snippets', snippetsByPrefix, source: filePath, scopeSelector: selector)

   getBodyParser: ->
     @bodyParser ?= require './snippet-body-parser'
@@ -169,6 +167,7 @@
   # Get a RegExp of all the characters used in the snippet prefixes
   wordRegexForSnippets: (snippets) ->
     prefixes = {}
+
     for prefix of snippets
       prefixes[character] = true for character in prefix
     prefixCharacters = Object.keys(prefixes).join('')


Renamed @definitions to @_definitions

Renamed @definitions to @_definitions

--- lib/tin-foil.coffee
+++ lib/tin-foil.coffee
@@ -8,7 +8,7 @@

   constructor: ->
     @properties = {}
-    @properties[name] = @buildProperty definition for own name, definition of @definitions
+    @properties[name] = @buildProperty definition for own name, definition of @_definitions

   buildProperty: (definition) ->
     name: definition.name
@@ -18,12 +18,12 @@
   @extend: (statics) ->
     class Result extends this
     Result.mixin(statics) if statics
-    Result.definitions = {}
+    Result._definitions = {}
     Result

   @mixin: (obj) ->
-    for own name, prop of obj.definitions
-      @definitions[name] = prop
+    for own name, prop of obj._definitions
+      @_definitions[name] = prop
       @_mapDefinitionAliases prop
     this

@@ -43,7 +43,7 @@
   @hasDefinition: (name) -> findDefinition name, this

   @define: (name, options, mapAliases = true) ->
-    @definitions ?= {}
+    @_definitions ?= {}

     if options
       type = options.as or options.type or {}
@@ -64,9 +64,9 @@
       defaultValue = new type

     # We don't want to re-create aliases if it already exists
-    if @definitions[name] then mapAliases = false
+    if @_definitions[name] then mapAliases = false

-    definition = @definitions[name] =
+    definition = @_definitions[name] =
       name: name
       type: type
       defaultValue: defaultValue
@@ -80,7 +80,7 @@
   @compile: (data) ->
     object = {}

-    for own name, property of @definitions
+    for own name, property of @_definitions
       val = null

       if property.type.isTinFoil
@@ -158,13 +158,13 @@

 findDefinition = (name, context) ->
-  definition = findLocalDefinition name, context.definitions
+  definition = findLocalDefinition name, context._definitions

   return definition if definition

   parent = context.__super__?.constructor
-  if parent and parent.definitions
     definition = findDefinition name, parent
+  if parent and parent._definitions

   definition


:bug: Fix validate for traces

:bug: Fix validate for traces

--- lib/validate.coffee
+++ lib/validate.coffee
@@ -40,5 +40,5 @@
       result.key = JSON.stringify(result)
       result.class = result.type.toLowerCase().replace(' ', '-')
       result.linter = linter.name
-      Validate.messages(result.trace) if result.trace and result.trace.length
+      Validate.messages(result.trace, linter) if result.trace and result.trace.length
     return undefined


Trigger event in async cart updates

Trigger event in async cart updates

--- app/assets/javascripts/glysellin/cart-recap.coffee
+++ app/assets/javascripts/glysellin/cart-recap.coffee
@@ -75,6 +75,8 @@
     # Total row handling
     @setTotals(resp)

+    @container.trigger('quantity-updated.glysellin')
+
   discountCodeUpdated: ->
     @update(
       'update-discount-code'
@@ -90,7 +92,8 @@
     )

   remoteAdjustmentUpdated: (resp) ->
-    if resp.adjustment_name
+    discount = resp.adjustment_name
+    if discount
       @subtotalsRow.fadeIn(200)
       @adjustmentRow.fadeIn(200)
     else
@@ -99,6 +102,8 @@

     # Total row handling
     @setTotals(resp)
+
+    @container.trigger('discount-updated.glysellin', [discount])

   update: (action, options, callback) ->
     $.post(


start focus point on close X for registration signup dialog

this fix should cover all of our student, parent, teacher
signup dialog forms.

fixes FOO-33
flag = none

test plan:
 - using chrome dev tools and responsive view
 - navigate to /register_from_website
 - click through each dialog form
 - verify focus point is set on the first visible
   navigable item (in this case it's the Close X button)

Change-Id: If04bde10558381c07f6265eab6454ada1649f57e
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/245699
Tested-by: Service Cloud Jenkins <9144042a601061f88f1e1d7a1753ea3e2972119d@instructure.com>
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
QA-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Product-Review: August Thornton <9adc7a1161ddf32ff608de792a7e50179545f026@instructure.com>

--- app/coffeescripts/registration/signupDialog.coffee
+++ app/coffeescripts/registration/signupDialog.coffee
@@ -94,7 +94,8 @@
     height: if screen.height > 750 then 'auto' else screen.height
     open: ->
       $(this).find('a').eq(0).blur()
-      $(this).find(':input').eq(0).focus()
+      # on open, provide focus to first focusable item for ease of accessibility
+      $(this).find('button.ui-dialog-titlebar-close').focus()
       if ENV.ACCOUNT.recaptcha_key
         $(this).find('.g-recaptcha')[0].addEventListener('load', (evt) ->
           # An explicit tabindex is needed for it to be tabbable in the dialog


Remove unused code

Remove unused code

--- app/scripts/components/generic/Choreography.coffee
+++ app/scripts/components/generic/Choreography.coffee
@@ -4,12 +4,10 @@
     @bind('GameLoop', @_choreographyTick)

     @_ctypes =
-      #bezier: @_executeBezier
       delay: @_executeDelay
       linear: @_executeLinear
       viewport: @_executeMoveIntoViewport
       viewportBezier: @_executeViewportBezier
-      tween: @_executeTween

   remove: ->
     return unless @_currentPart?
@@ -95,9 +93,12 @@
         currentProperties[k] = @[k]
       @_currentPart.currentProperties = currentProperties

-  _executeLinear: (v) ->
-    @x = @_currentPart.x + (v * (@_currentPart.dx ? 0))
-    @y = @_currentPart.y + (v * (@_currentPart.dy ? 0))
+  _executeLinear: (v, prevv) ->
+    dx = (v * (@_currentPart.dx ? 0)) - (prevv * (@_currentPart.dx ? 0))
+    dy = (v * (@_currentPart.dy ? 0)) - (prevv * (@_currentPart.dy ? 0))
+
+    @x += dx
+    @y += dy

   _executeDelay: (v) ->

@@ -110,10 +111,6 @@
       motionX = (diffX * v)
       pmotionX = (diffX * prevv)

-      #@shiftedX ?= 0
-      #@shiftedX = Math.max(0, @shiftedX - .5)
-
-      #@x = @_currentPart.moveOriginX + motionX - Crafty.viewport.x + @shiftedX - Crafty.viewport.xShift
       @x += motionX - pmotionX #+ @shiftedX - Crafty.viewport.xShift

     destinationY = @_currentPart.dy
@@ -124,32 +121,7 @@
       motionY = (diffY * v)
       pmotionY = (diffY * prevv)

-      #@y = @_currentPart.moveOriginY + motionY - Crafty.viewport.y - Crafty.viewport.yShift
       @y += motionY - pmotionY
-
-  _executeTween: (v) ->
-    for k, p of @_currentPart.properties
-      @[k] = (1 - v) * @_currentPart.currentProperties[k] + (v * p)
-
-  #_executeBezier: (v) ->
-    #bp = new Game.BezierPath
-    #unless @_currentPart.bPath?
-      #scaled = bp.scalePoints(@_currentPart.path,
-        #origin:
-          #x: @x
-          #y: @y
-        #scale:
-          #x: Crafty.viewport.width
-          #y: Crafty.viewport.height
-      #)
-      #@_currentPart.bPath = bp.buildPathFrom scaled
-    #point = bp.pointOnPath(@_currentPart.bPath, v)
-
-    #if @_currentPart.rotation
-      #@rotation = bp.angleOnPath(@_currentPart.bPath, v)
-
-    #@x = point.x
-    #@y = point.y

   _executeViewportBezier: (v, prevv) ->
     bp = new Game.BezierPath


Add some additional test cases. Run them through both notification strategies

--- src/test/hubot-jenkins-notifier_test.coffee
+++ src/test/hubot-jenkins-notifier_test.coffee
@@ -60,37 +60,75 @@
   user.should.be.a('object').and.have.property('name', 'tj')
 ###

+test_data = [
+  {
+    "name": "finished-failed",
+    "expected_out": "JobName build #1 started failing (http://ci.jenkins.org/job/project/5)",
+    "body": {
+      "name":"JobName",
+      "url":"JobUrl",
+      "build":{
+        "number":1,
+        "phase":"FINISHED",
+        "status":"FAILURE",
+        "url":"job/project/5",
+        "full_url":"http://ci.jenkins.org/job/project/5"
+        "parameters":{"branch":"master"}
+      }
+    }
+  },
+  {
+    "name": "started-failed",
+    "expected_out": false,
+    "body": {
+      "name":"JobName",
+      "url":"JobUrl",
+      "build":{
+        "number":2,
+        "phase":"STARTED",
+        "status":"FAILED",
+        "url":"job/project/5",
+        "full_url":"http://ci.jenkins.org/job/project/5"
+        "parameters":{"branch":"master"}
+      }
+    }
+  }
+]

-finished_failed_body = {
-  "name":"JobName",
-  "url":"JobUrl",
-  "build":{
-    "number":1,
-    "phase":"FINISHED",
-    "status":"FAILURE",
-    "url":"job/project/5",
-    "full_url":"http://ci.jenkins.org/job/project/5"
-    "parameters":{"branch":"master"}
-  }
+urls = {
+  'new' : "/hubot/jenkins-notify?room=%23halkeye&always_notify=1",
+  'old' : "/hubot/jenkins-notify?room=%23halkeye&notstrat=FS"
 }

-describe 'finished-failed', ()->
-  before (done) ->
-    robot.adapter.send = sinon.spy()
-    endfunc = (err, res) ->
-      throw err if err
-      do done
-    request(robot.router)
-      .post("/hubot/jenkins-notify?room=%23halkeye&always_notify=1")
-      .send(JSON.stringify(finished_failed_body))
-      .expect(200)
-      .end(endfunc)
-  it 'Robot sent out respond', ()->
-    robot.adapter.send.called.should.be.true
-  it 'Robot sent to right room', ()->
-    send_arg = robot.adapter.send.getCall(0).args[0]
-    send_arg.user.room.should.eql '#halkeye'
-    send_arg.room.should.eql '#halkeye'
-  it 'Robot sent right message', ()->
-    robot.adapter.send.getCall(0).args[1].should.eql "JobName build #1 started failing (http://ci.jenkins.org/job/project/5)"
+for test in test_data then do (test) ->
+  for url_type,url of urls then do (url_type, url) ->
+    describe test.name + '-' + url_type, ()->
+      before (done) ->
+        robot.jenkins_notifier.reset()
+        robot.adapter.send = sinon.spy()
+        endfunc = (err, res) ->
+          throw err if err
+          do done
+        request(robot.router)
+          .post(url)
+          .send(JSON.stringify(test.body))
+          .expect(200)
+          .end(endfunc)
+      it 'Robot sent out respond', ()->
+        if test.expected_out == false
+          robot.adapter.send.called.should.be.false
+        else
+          robot.adapter.send.called.should.be.true
+      it 'Robot sent to right room', ()->
+        if test.expected_out == false
+          # previous test will test this
+        else
+          send_arg = robot.adapter.send.getCall(0).args[0]
+          send_arg.user.room.should.eql '#halkeye'
+          send_arg.room.should.eql '#halkeye'
+      it 'Robot sent right message', ()->
+        if test.expected_out == false
+          # previous test will test this
+        else
+          robot.adapter.send.getCall(0).args[1].should.eql test.expected_out


Added refresh on cocoon:after-insert in intervention.js

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -133,6 +133,8 @@
         #date = $(this).data("datetimepicker").getFormattedDate()
         date = $(this).val()
         E.interventions.updateDateScopes(date)
+    $('*[data-intervention-updater]').each ->
+        E.interventions.refresh $(this)

   $(document).on 'mapchange', '*[data-intervention-updater]', ->
     $(this).each ->


reenable form wells on bike edit

reenable form wells on bike edit

--- app/assets/javascripts/revised/pages/bikes/edit.coffee
+++ app/assets/javascripts/revised/pages/bikes/edit.coffee
@@ -1,6 +1,6 @@
 class BikeIndex.BikesEdit extends BikeIndex
   constructor: ->
-    # new BikeIndex.FormWell
+    new BikeIndex.FormWell
     @initializeEventListeners()

     # Initialize dirty forms. Add class 'dirtyignore' to fields to ignore them


Add instance tracking (cid and id mapping) to Marbles.Collection

--- src/marbles/collection.js.coffee
+++ src/marbles/collection.js.coffee
@@ -2,11 +2,87 @@
   @model: Marbles.Model
   model_ids: new Array

+  @instances: {
+    all: {}
+  }
+  @id_mapping: {}
+  @id_mapping_scope: ['cid']
+  @_id_counter: 0
+
+  @buildIdMappingScope: (params) ->
+    scope = []
+    for key in @id_mapping_scope
+      return null unless params.hasOwnProperty(key)
+      scope.push params[key]
+    scope.join(":")
+
+  @find: (params) ->
+    id_scope = @buildIdMappingScope(params)
+    if id_scope && (cid = @id_mapping[@collection_name]?[id_scope])
+      params.cid = cid
+
+    @instances.all[params.cid]
+
+  # delete reference
+  @detach: (cid) ->
+    delete @instances.all[cid]
+
+    if index = @instances[@collection_name]?.indexOf(cid)
+      instances = @instances[@collection_name]
+      instances = instances.slice(0, index).concat(instances.slice(index+1, instances.length))
+      @instances[@collection_name] = instances
+
+    for _id, _cid of @id_mapping
+      if _cid == cid
+        delete @id_mapping[_id]
+        break
+
   @buildModel: (attrs) ->
     new @model(attrs)

-  constructor: (options = {}) ->
-    @resetRaw(options.raw) if _.isArray(options.raw)
+  constructor: (@options = {}) ->
+    @generateCid()
+    @trackInstance()
+    for key in @constructor.id_mapping_scope
+      @on "change:#{key}", @updateIdMapping
+
+    @resetRaw(@options.raw) if _.isArray(@options.raw)
+
+  generateCid: =>
+    unless @constructor.collection_name
+      @constructor.collection_name = @constructor.model.model_name + '_collection'
+
+    if @options.cid
+      @cid = @options.cid
+      delete @options.cid
+    else
+      @cid = "#{@constructor.collection_name}_#{@constructor._id_counter++}"
+
+  trackInstance: =>
+    @constructor.instances.all[@cid] = @
+    @constructor.instances[@constructor.model_name] ?= []
+    @constructor.instances[@constructor.model_name].push @cid
+
+  updateIdMapping: (new_value, old_value, attr) =>
+    old_scope = []
+    new_scope = []
+    for key in @constructor.id_mapping_scope
+      if key == attr
+        old_scope.push old_value
+        new_scope.push new_value
+      else
+        old_scope.push @get(key)
+        new_scope.push @get(key)
+    old_scope = old_scope.join(":")
+    new_scope = new_scope.join(":")
+
+    @constructor.id_mapping[@constructor.collection_name] ?= {}
+    delete @constructor.id_mapping[@constructor.collection_name][old_scope]
+    @constructor.id_mapping[@constructor.collection_name][new_scope] = @cid
+
+  detach: =>
+    @constructor.detach(@cid)
+    @trigger 'detach', @

   fetch: (params = {}, options = {}) =>
     throw new Error("You need to define #{@constructor.name}::fetch(params, options)!")
@@ -85,4 +161,5 @@
     models

 _.extend Collection::, Marbles.Events
+_.extend Collection::, Marbles.Accessors


DateTextBox leaves invalid text alone.

DateTextBox leaves invalid text alone.

--- port/DateTextBox.ValidatingTextBox.coffee
+++ port/DateTextBox.ValidatingTextBox.coffee
@@ -41,9 +41,9 @@
     content = @content()
     date = @_parseDate content
     @date date
-    if content and content.length > 0
+    if content?.length > 0
       # If a date is supplied, it has to be valid.
-      valid = valid and !!date
+      valid = valid and date?
     valid

   # Use a culture's "short date" pattern (e.g., "M/d/yyyy") to determine some
@@ -124,9 +124,12 @@

   _refresh: ->
     date = @date()
-    newContent = if date? then @_formatDate( date ) else ""
-    if @content() isnt newContent
-      @content newContent
+    if date?
+      # The user entered a valid date; show it in its canonical formatted form.
+      formattedDate = @_formatDate date
+      unless @content() == formattedDate
+        # Prefer the canonical form over what the user typed.
+        @content formattedDate
     @

   _previousDate: Control.property.date()


payment: don't assign formatted price to data

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domainbuyitem.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domainbuyitem.coffee
@@ -5,7 +5,6 @@

     { price } = data

-    data.price = @utils.formatMoney +price
     super options, data

     selectOptions = \


Client: Teams: Added default hidden class to noItemFoundWidget

--- client/admin/lib/views/members/adminmembersview.coffee
+++ client/admin/lib/views/members/adminmembersview.coffee
@@ -27,11 +27,11 @@
     tabView.addPane blocked = new kd.TabPaneView name: 'Blocked'

     all.addSubView @allView = new TeamMembersCommonView
-        partial         : 'No members found!'
-        cssClass        : 'no-item-view'
       fetcherMethod          : 'fetchMembersWithEmail'
       showSearchFieldAtFirst : yes
       noItemFoundWidget      : new kd.CustomHTMLView
+        partial              : 'No members found!'
+        cssClass             : 'no-item-view hidden'
     , data

     admins.addSubView @adminsView = new TeamMembersCommonView


BaseStackTemplateList: allow delete option by default

--- client/app/lib/stacks/basestacktemplatelistitem.coffee
+++ client/app/lib/stacks/basestacktemplatelistitem.coffee
@@ -40,8 +40,6 @@
     @addMenuItem 'Show', listView.lazyBound 'showItemContent', this
     @addMenuItem 'Edit', @bound 'editStackTemplate'
     @addMenuItem 'Generate Stack', @bound 'generateStackFromTemplate'
-
-    if listView.items.length > 1
-      @addMenuItem 'Delete',  listView.lazyBound 'deleteItem', this
+    @addMenuItem 'Delete', listView.lazyBound 'deleteItem', this

     return @menu


Global link hints; fix focus issue for options page.

Here's the issue (and we may have to address this ouside of this PR).

If we put the HUD in the top frame, then the top frame grabs the focus
when the HUD is displayed.

If we open link hints with the help dialog open, then the help dialog
loses the focus, and we can't `Esc` out of it.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -71,7 +71,7 @@

   postKeyState: (request) -> @linkHintsMode.postKeyState request
   activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker
-  setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], true
+  setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], false
   getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null

   exit: ->
@@ -140,7 +140,7 @@
           (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ])
         HintCoordinator.sendMessage "exitFailure"

-    @setOpenLinkMode mode, true
+    @setOpenLinkMode mode, false

     # Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
     # because some clickable elements cannot contain children, e.g. submit buttons.
@@ -150,9 +150,9 @@
     @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId
     @postKeyState = @postKeyState.bind this, hintMarkers

-  setOpenLinkMode: (@mode, doNotPropagate = false) ->
-    @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame()
-    unless doNotPropagate
+  setOpenLinkMode: (@mode, shouldPropagtetoOtherFrames = true) ->
+    @hintMode.setIndicator @mode.indicator if windowIsFocused()
+    if shouldPropagtetoOtherFrames
       HintCoordinator.sendMessage "setMode", modeIndex: availableModes.indexOf @mode

   #


Filter filtered-mode link hints.

Filter filtered-mode link hints.

Only keep non-empty link words.  Empty link words cannot be matched, and
leading empty link words disrupt the scoring of matches at the start of
the text.

E.g. "9 new messages" with a query "new" should score as a match at the
start of the text.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -501,7 +501,9 @@
   scoreLinkHint: (linkSearchString) ->
     searchWords = linkSearchString.trim().toLowerCase().split @splitRegexp
     (linkMarker) =>
-      linkWords = linkMarker.linkWords ?= linkMarker.linkText.toLowerCase().split @splitRegexp
+      # We only keep non-empty link words.  Empty link words cannot be matched, and leading empty link words
+      # disrupt the scoring of matches at the start of the text.
+      linkWords = linkMarker.linkWords ?= linkMarker.linkText.toLowerCase().split(@splitRegexp).filter (term) -> term

       searchWordScores =
         for searchWord in searchWords


landing: fixed grammar error

landing: fixed grammar error

--- client/landing/site.landing/coffee/home/AppView.coffee
+++ client/landing/site.landing/coffee/home/AppView.coffee
@@ -51,7 +51,7 @@
       },
       {
         position : [10, 17] #percent
-        content  : 'Each new workspace gets its own set if IDE and Terminal tabs \n so you can easily manage different projects.'
+        content  : 'Each new workspace gets its own set of IDE and Terminal tabs \n so you can easily manage different projects.'
       }
     ]
     #Channels


Refactor Problem from changes upstream

Refactor Problem from changes upstream

--- templates/coffee/src/modules/problem.coffee
+++ templates/coffee/src/modules/problem.coffee
@@ -38,10 +38,20 @@
       @render(content)

   show: =>
-    Logger.log 'problem_show', problem: @id
-    $.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) =>
-      $.each response, (key, value) =>
-        @$("#answer_#{key}").text(value)
+    if !@element.hasClass 'showed'
+      Logger.log 'problem_show', problem: @id
+      $.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) =>
+        $.each response, (key, value) =>
+          if $.isArray(data[key])
+            $.each data[key], (index, answer_index) =>
+              @$("#label[for='input_#{key}_#{data[key][answer_index]}']").attr
+                correct_answer: 'true'
+          @$("#answer_#{key}").text(value)
+        @element.addClass 'showed'
+    else
+      @$('[id^=answer_]').text('')
+      @$('[correct_answer]').attr(correct_answer: null)
+

   save: =>
     Logger.log 'problem_save', @answers
@@ -50,8 +60,8 @@
         alert 'Saved'

   refreshAnswers: =>
-    @answers = {}
     @$('input.schematic').each (index, element) ->
       element.schematic.update_value()
-    $.each @$("[id^=input_#{@id}_]"), (index, input) =>
-      @answers[$(input).attr('id')] = $(input).val()
+    @$(".CodeMirror").each (index, element) ->
+      element.CodeMirror.save() if element.CodeMirror.save
+    @answers = @$("[id^=input_#{@id}_]").serialize()


simplify fix for #4

simplify fix for #4

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -220,17 +220,11 @@
     if e.type == 'focus'
       # do this only if placeholder is currently in DOM
       unless @placeholderDetached
+        @placeholderDetached = true
         # the following code should be called detached from this focus callback function
         # otherwise rangy failed because focus isn't finished and caret has not been placed yet
-        window.setTimeout(=>
-          # detach placeholder element
-          @$placeholder.detach()
-          @placeholderDetached = true
-          # create textnode and prepend it to the $editable
-          # neccessary to place caret properly before detaching the placeholder
-          textNode = document.createTextNode('')
-          @$editable.html(textNode).focus()
-        )
+        window.setTimeout(=> @$placeholder.detach())
+
     else if e.type == 'blur' || e.type == 'reset'
       if @_strip(@serializeEditable()) == ''
         @$editable.empty().append(@$placeholder)


Fixed #59

Fixed #59

--- lib/views/serial-monitor-view.coffee
+++ lib/views/serial-monitor-view.coffee
@@ -93,6 +93,10 @@
           option.attr 'selected', 'selected'
         @portsSelect.append option

+      if ports.length > 0
+        @currentPort ?= ports[0].comName
+        SettingsHelper.set 'serial_port', @currentPort
+
   portSelected: ->
     @currentPort = @portsSelect.val()
     SettingsHelper.set 'serial_port', @currentPort


BaseFactory#modelProperties

BaseFactory#modelProperties

--- src/lib/base-factory.coffee
+++ src/lib/base-factory.coffee
@@ -25,6 +25,26 @@
     @type String
     ###
     @modelName: null
+
+
+    ###*
+    key-value pair of prop name -> type info  when prop is model/models
+
+    @property modelProperties
+    @protected
+    @type Object
+    ###
+    constructor: ->
+        @modelProperties = {}
+
+        propertyInfo = @getModelClass().properties
+
+        for prop, type of propertyInfo
+            typeInfo = TYPES.info(type)
+            if typeInfo.model
+                @modelProperties[prop] = typeInfo
+
+

     ###*
@@ -109,9 +129,7 @@

         propertyInfo = @getModelClass().properties

-        typeInfo = TYPES.info(propertyInfo[prop])
-
-        if typeInfo.model?
+        if typeInfo = @modelProperties[prop]

             subModelFactory = @getFacade().createFactory(typeInfo.model)


operations: fix Notify callback result

operations: fix Notify callback result

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -1078,7 +1078,7 @@
                                 # Content already exists, perhaps
                                 # relaying a notification from another
                                 # service
-                                return cb4()
+                                return cb4 null, { id, el }

                             userSubscriptions = subscriptions.filter (subscription) ->
                                 subscription.node.indexOf("/user/#{id}/") is 0


event: reduce request concurrency

event: reduce request concurrency

--- models/event.coffee
+++ models/event.coffee
@@ -113,7 +113,7 @@
             QUERY_EXPIRE_TIME_SECONDS
       .then ->
         # Actually query InfluxDB and store the results in redis
-        Promise.map uncached, (query) ->
+        Promise.each uncached, (query) ->
           totalElapsedMs = new Date() - batchStartTime
           if totalElapsedMs > QUERY_EXPIRE_TIME_SECONDS * 1000
             skipCount += 1
@@ -136,7 +136,6 @@
               query
               elapsed: new Date() - startTime
             }
-        , {concurrency: OS_CPUS}
       .then ->
         log.info {
           event: 'batch_completed'


covert angle to radians

covert angle to radians

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -140,16 +140,17 @@
     @inity = initial_y
     @x = 0
     @y = 0
-    @g = -9.80665
+    @g = 9.8

-    F = 40
-    angle = 70
+    F = 13
+    angle = 45.0
+    radian = angle*Math.PI/180

-    @dx = F/Math.cos(angle)
-    @dy = F/Math.sin(angle)
+    @dx = F/Math.cos(radian)
+    @dy = F/Math.sin(radian)

   animate: ->
-    @draw_frame()
+    setTimeout (=> @draw_frame()), 150

   draw: ->
     @context.drawImage @image(), @initx+@x, @inity-@y
@@ -158,11 +159,11 @@
     @draw()
     @calculate_projection()

-    setTimeout (=> @draw_frame()), 100
+    setTimeout (=> @draw_frame()), 150

   calculate_projection:() ->
     @x += @dx
-    @dy += @g
+    @dy -= @g
     @y += @dy

   image:() ->


fix i18n typo

fix i18n typo

--- views/henseiItem.cjsx
+++ views/henseiItem.cjsx
@@ -120,7 +120,7 @@

{los}{__ ' Old'}

                   </Tooltip>
                 }>
-                  {__ 'LOS'}: {los}
+                  {__ 'LOS '}: {los}
                 </OverlayTrigger>

           }


bugfix: don't include guests in the member count

--- client/app/Applications/Home.kdapplication/AppView.coffee
+++ client/app/Applications/Home.kdapplication/AppView.coffee
@@ -42,12 +42,13 @@
     vmController = KD.getSingleton("vmController")
     {JAccount, JTag, JGroup, CActivity} = KD.remote.api

-    members.ready => JAccount.count             (err, count)=> members.update count    or 0
-    vms.ready => vmController.fetchTotalVMCount (err, count)=> vms.update count        or 0
-    # loc.ready => vmController.fetchTotalLoC     (err, count)=> loc.update count        or 0
-    groups.ready => JGroup.count                (err, count)=> groups.update count     or 0
-    topics.ready => JTag.fetchCount             (err, count)=> topics.update count     or 0
-    activities.ready => CActivity.fetchCount    (err, count)=> activities.update count or 0
+    members.ready ->
+      JAccount.count type: 'registered',        (err, count)=> members.update count    or 0
+    vms.ready -> vmController.fetchTotalVMCount (err, count)=> vms.update count        or 0
+    # loc.ready -> vmController.fetchTotalLoC     (err, count)=> loc.update count        or 0
+    groups.ready -> JGroup.count                (err, count)=> groups.update count     or 0
+    topics.ready -> JTag.fetchCount             (err, count)=> topics.update count     or 0
+    activities.ready -> CActivity.fetchCount    (err, count)=> activities.update count or 0

     KD.getSingleton("activityController").on "ActivitiesArrived", (newActivities=[])->
       activities.increment newActivities.length


Rename variable

Rename variable

--- client/app/lib/activity/sidebar/sidebarmachinelist.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinelist.coffee
@@ -59,17 +59,17 @@

     return no  if @machineBoxesByMachineUId[uid]

-    machineBox = new SidebarMachineBox {}, machineAndWorkspaceData
-    @addSubView machineBox
-    @machineBoxes.push machineBox
-    @machineBoxesByMachineUId[uid] = machineBox
+    box = new SidebarMachineBox {}, machineAndWorkspaceData
+    @addSubView box
+    @machineBoxes.push box
+    @machineBoxesByMachineUId[uid] = box

-    machineBox.once 'KDObjectWillBeDestroyed', =>
-      @machineBoxes.splice @machineBoxes.indexOf(machineBox), 1
-      delete @machineBoxesByMachineUId[machineBox.machine.uid]
-      @emit 'MachineBoxDestroyed', machineBox
+    box.once 'KDObjectWillBeDestroyed', =>
+      @machineBoxes.splice @machineBoxes.indexOf(box), 1
+      delete @machineBoxesByMachineUId[box.machine.uid]
+      @emit 'MachineBoxDestroyed', box

-    machineBox.on 'ListStateChanged', => @emit 'ListStateChanged'
+    box.on 'ListStateChanged', => @emit 'ListStateChanged'

   removeWorkspaceByChannelId: (channelId) ->


wait before signing in

wait before signing in

When sign in succeeds, it waits for 0.5s so the use can see the message telling
that the sign in succeeded.

--- client/app/views/auth/index.coffee
+++ client/app/views/auth/index.coffee
@@ -64,7 +64,9 @@

         @model.success
             .doAction =>
-                window.location.pathname = @options.next
+                setTimeout =>
+                    window.location.pathname = @options.next
+                , 500
             .map =>
                 $ '<i/>',
                     class: 'fa fa-check'
@@ -79,3 +81,4 @@
         setTimeout =>
             @ui.passwd.focus()
         , 100
+


Removing disabled code.

Removing disabled code.

--- client/views/app/flexTabBar.coffee
+++ client/views/app/flexTabBar.coffee
@@ -25,15 +25,3 @@
 			RocketChat.TabBar.setTemplate $(e.currentTarget).data('template'), ->
 				$('.flex-tab')?.find("input[type='text']:first")?.focus()
 				$('.flex-tab .content')?.scrollTop(0)
-
-		# if Session.get('flexOpened') and Session.equals('whichFlexOpened', $(e.currentTarget).data('target'))
-		# 	Session.set('rtcLayoutmode', 0)
-		# 	Session.set('flexOpened',false)
-		# 	Session.set('whichFlexOpened')
-		# else
-		# 	$(e.currentTarget).addClass 'active'
-		# 	Session.set('flexOpened', true)
-		# 	Session.set('whichFlexOpened', $(e.currentTarget).data('target'))
-
-		# # $('.user-info-content').hide()
-		# # $($(e.currentTarget).attr('href')).show()


tests: Batch device tests re-creating a multicontainer app

Change-type: patch
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- tests/integration/models/device.spec.coffee
+++ tests/integration/models/device.spec.coffee
@@ -9,7 +9,7 @@
 	givenAnApplication
 	givenAnApplicationWithADevice
 	givenLoggedInUser
-	givenMulticontainerApplicationWithADevice
+	givenMulticontainerApplication
 	sdkOpts
 	IS_BROWSER
 } = require('../setup')
@@ -1148,11 +1148,13 @@
 				promise = balena.models.device.getSupervisorState(@device.id)
 				m.chai.expect(promise).to.be.rejectedWith('No online device(s) found')

-	describe 'given a multicontainer application with a single offline device', ->
-
-		describe '[contained scenario]', ->
-
-			givenMulticontainerApplicationWithADevice(before)
+	describe 'given a multicontainer application', ->
+
+		givenMulticontainerApplication(before)
+
+		describe 'given a single offline device', ->
+
+			givenADevice(before)

 			describe 'balena.models.device.getWithServiceDetails()', ->

@@ -1342,65 +1344,65 @@
 					promise = balena.models.device.getTargetReleaseHash(@device.uuid)
 					m.chai.expect(promise).to.eventually.equal('new-release-commit')

-		describe 'balena.models.device.pinToRelease()', ->
-
-			givenMulticontainerApplicationWithADevice(beforeEach)
-
-			it 'should set the device to a specific release, using the device id & release commit', ->
-				balena.models.device.pinToRelease(@device.id, 'old-release-commit')
-				.then =>
-					promise = balena.models.device.getTargetReleaseHash(@device.id)
-					m.chai.expect(promise).to.eventually.equal('old-release-commit')
-				.then =>
-					promise = balena.models.device.isTrackingApplicationRelease(@device.id)
-					m.chai.expect(promise).to.eventually.be.false
-
-			it 'should set the device to a specific release, using the device id & release id', ->
-				balena.models.device.pinToRelease(@device.id, @oldRelease.id)
-				.then =>
-					promise = balena.models.device.getTargetReleaseHash(@device.id)
-					m.chai.expect(promise).to.eventually.equal('old-release-commit')
-				.then =>
-					promise = balena.models.device.isTrackingApplicationRelease(@device.id)
-					m.chai.expect(promise).to.eventually.be.false
-
-			it 'should set the device to a specific release, using the device uuid & release commit', ->
-				balena.models.device.pinToRelease(@device.uuid, 'old-release-commit')
-				.then =>
-					promise = balena.models.device.getTargetReleaseHash(@device.id)
-					m.chai.expect(promise).to.eventually.equal('old-release-commit')
-				.then =>
-					promise = balena.models.device.isTrackingApplicationRelease(@device.id)
-					m.chai.expect(promise).to.eventually.be.false
-
-			it 'should set the device to a specific release, using the device uuid & release id', ->
-				balena.models.device.pinToRelease(@device.uuid, @oldRelease.id)
-				.then =>
-					promise = balena.models.device.getTargetReleaseHash(@device.id)
-					m.chai.expect(promise).to.eventually.equal('old-release-commit')
-				.then =>
-					promise = balena.models.device.isTrackingApplicationRelease(@device.id)
-					m.chai.expect(promise).to.eventually.be.false
-
-		describe 'balena.models.device.trackApplicationRelease()', ->
-
-			givenMulticontainerApplicationWithADevice(beforeEach)
-
-			it 'should set the device to track the current application release, using the device id', ->
-				balena.models.device.pinToRelease(@device.id, 'old-release-commit')
-				.then =>
-					balena.models.device.trackApplicationRelease(@device.id)
-				.then =>
-					promise = balena.models.device.isTrackingApplicationRelease(@device.id)
-					m.chai.expect(promise).to.eventually.be.true
-
-			it 'should set the device to track the current application release, using the device uuid', ->
-				balena.models.device.pinToRelease(@device.id, 'old-release-commit')
-				.then =>
-					balena.models.device.trackApplicationRelease(@device.uuid)
-				.then =>
-					promise = balena.models.device.isTrackingApplicationRelease(@device.id)
-					m.chai.expect(promise).to.eventually.be.true
+		describe 'given a newly registered offline device', ->
+
+			givenADevice(beforeEach)
+
+			describe 'balena.models.device.pinToRelease()', ->
+
+				it 'should set the device to a specific release, using the device id & release commit', ->
+					balena.models.device.pinToRelease(@device.id, 'old-release-commit')
+					.then =>
+						promise = balena.models.device.getTargetReleaseHash(@device.id)
+						m.chai.expect(promise).to.eventually.equal('old-release-commit')
+					.then =>
+						promise = balena.models.device.isTrackingApplicationRelease(@device.id)
+						m.chai.expect(promise).to.eventually.be.false
+
+				it 'should set the device to a specific release, using the device id & release id', ->
+					balena.models.device.pinToRelease(@device.id, @oldRelease.id)
+					.then =>
+						promise = balena.models.device.getTargetReleaseHash(@device.id)
+						m.chai.expect(promise).to.eventually.equal('old-release-commit')
+					.then =>
+						promise = balena.models.device.isTrackingApplicationRelease(@device.id)
+						m.chai.expect(promise).to.eventually.be.false
+
+				it 'should set the device to a specific release, using the device uuid & release commit', ->
+					balena.models.device.pinToRelease(@device.uuid, 'old-release-commit')
+					.then =>
+						promise = balena.models.device.getTargetReleaseHash(@device.id)
+						m.chai.expect(promise).to.eventually.equal('old-release-commit')
+					.then =>
+						promise = balena.models.device.isTrackingApplicationRelease(@device.id)
+						m.chai.expect(promise).to.eventually.be.false
+
+				it 'should set the device to a specific release, using the device uuid & release id', ->
+					balena.models.device.pinToRelease(@device.uuid, @oldRelease.id)
+					.then =>
+						promise = balena.models.device.getTargetReleaseHash(@device.id)
+						m.chai.expect(promise).to.eventually.equal('old-release-commit')
+					.then =>
+						promise = balena.models.device.isTrackingApplicationRelease(@device.id)
+						m.chai.expect(promise).to.eventually.be.false
+
+			describe 'balena.models.device.trackApplicationRelease()', ->
+
+				it 'should set the device to track the current application release, using the device id', ->
+					balena.models.device.pinToRelease(@device.id, 'old-release-commit')
+					.then =>
+						balena.models.device.trackApplicationRelease(@device.id)
+					.then =>
+						promise = balena.models.device.isTrackingApplicationRelease(@device.id)
+						m.chai.expect(promise).to.eventually.be.true
+
+				it 'should set the device to track the current application release, using the device uuid', ->
+					balena.models.device.pinToRelease(@device.id, 'old-release-commit')
+					.then =>
+						balena.models.device.trackApplicationRelease(@device.uuid)
+					.then =>
+						promise = balena.models.device.isTrackingApplicationRelease(@device.id)
+						m.chai.expect(promise).to.eventually.be.true

 	describe 'given a single application with a device id whose shorter uuid is only numbers', ->


Change imported name from "math" to "bokeh_math" to make it more obvious this is not related to the core JS "Math" facility.

--- bokehjs/src/coffee/models/transforms/jitter.coffee
+++ bokehjs/src/coffee/models/transforms/jitter.coffee
@@ -1,7 +1,7 @@
 _ = require "underscore"
 Transform = require "./transform"
 p = require "../../core/properties"
-math = require "../../core/util/math"
+bokeh_math = require "../../core/util/math"

 class Jitter extends Transform.Model
   initialize: (attrs, options) ->
@@ -19,7 +19,7 @@
         return(x + @get('mean') + ((Math.random() - 0.5) * @get('width')))

     if @get('distribution') == 'normal'
-        return(x + math.rnorm(@get('mean'), @get('width')))
+        return(x + bokeh_math.rnorm(@get('mean'), @get('width')))

   v_compute: (xs) ->
     # Apply the tranform to a vector of values


Add unit test for rejected collection names

--- test/collection-spec.coffee
+++ test/collection-spec.coffee
@@ -39,6 +39,18 @@
         .expect(200)
         .end (err, res) ->
           throw err if err
+          done() if --count is 0
+
+  it 'should restrict access to unknown collections', (done) ->
+    cols = ['system.test', 'test', 'admin', '_test', 'æøå']
+    count = cols.length
+
+    for col in cols
+      req.get(url.replace('turer', col))
+        .expect(404)
+        .end (err, res) ->
+          throw err if err
+          assert.equal res.body.message, 'Objekttype ikke funnet'
           done() if --count is 0

   it 'should return a limited set of document properties', (done) ->


Add docuemnt property unit test for GET collection

--- test/collection-spec.coffee
+++ test/collection-spec.coffee
@@ -26,6 +26,20 @@
       .end (err, res) ->
         throw err if err
         assert.equal res.body.documents.length, 20
+        done()
+
+  it 'should return a limited set of document properties', (done) ->
+    req.get(url)
+      .expect(200)
+      .end (err, res) ->
+        throw err if err
+
+        for doc in res.body.documents
+          assert.equal Object.keys(doc).length, 3
+          assert.equal typeof doc._id, 'string'
+          assert.equal typeof doc.navn, 'string'
+          assert.equal typeof doc.endret, 'string'
+
         done()

   it 'should handle improper formated parameters', (done) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -25,6 +25,7 @@
     x: 4
     y: 20

-  equals engine.first('type=testObj').length, obj1
+  equals engine.first('type=testObj').length, 1
+  equ

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -8,7 +8,7 @@
     x: 0
     y: 50

-  equals engine.find('.findable').length,
+  equals engine.find('.findable').length, 1

 module()


fixes annoying perDiem typo once and for all


jQuery platform replaces overrides addEventListener.

--- src/platform/jquery.coffee
+++ src/platform/jquery.coffee
@@ -13,6 +13,12 @@

   textContent: (node) ->
     jQuery(node).text()
+
+  addEventListener: (node, eventName, callback) ->
+    $(node).on(eventName, callback)
+
+  removeEventListener: (node, eventName, callback) ->
+    $(node).off(eventName, callback)

 Batman.View.accessor '$node', ->
   $(@node) if @get('node')


Add missing mdMedia

Add missing mdMedia

--- angular/core/components/sidebar/sidebar.coffee
+++ angular/core/components/sidebar/sidebar.coffee
@@ -3,7 +3,7 @@
   restrict: 'E'
   templateUrl: 'generated/components/sidebar/sidebar.html'
   replace: true
-  controller: ($scope, Session, $rootScope, $window, RestfulClient, ThreadQueryService, UserHelpService, AppConfig, IntercomService, $mdSidenav, LmoUrlService, Records, ModalService, GroupForm) ->
+  controller: ($scope, Session, $rootScope, $window, RestfulClient, ThreadQueryService, UserHelpService, AppConfig, IntercomService, $mdMedia, $mdSidenav, LmoUrlService, Records, ModalService, GroupForm) ->
     $scope.currentState = ""
     $scope.showSidebar = true


AdminModal: Show jGroup logo if exist.

AdminModal: Show jGroup logo if exist.

--- client/admin/lib/views/groupgeneralsettingsview.coffee
+++ client/admin/lib/views/groupgeneralsettingsview.coffee
@@ -125,13 +125,24 @@
     @addSubView section = createSection
       name : 'avatar-upload'

-    section.addSubView new KDCustomHTMLView
+    section.addSubView @avatar = new KDCustomHTMLView
       cssClass : 'avatar'

     section.addSubView new KDButtonView
       cssClass : 'compact solid green upload'
       title    : 'UPLOAD IMAGE'

+    @setAvatar()
+
+
+  setAvatar: ->
+
+    avatarEl = @avatar.getElement()
+    jGroup   = @getData()
+    logo     = jGroup.customize?.logo
+
+    if logo
+      avatarEl.style.backgroundImage = "url(#{logo})"

   createDeletionForm: ->


Add default page legth to 25 for datatable for readability

--- app/assets/javascripts/data-tables-init.coffee
+++ app/assets/javascripts/data-tables-init.coffee
@@ -7,6 +7,7 @@
     options =
       responsive: true
       order: [[0, "desc"]]
+      pageLength: 25

     ascColumn = table.find("th.sort-asc").index()
     descColumn = table.find("th.sort-desc").index()


typos

typos

--- app/assets/javascripts/geometry_factory.coffee
+++ app/assets/javascripts/geometry_factory.coffee
@@ -7,7 +7,7 @@
 					unless err
 						mesh.setVertices (View.createArrayBufferObject vertices), vertices.length
 						mesh.setColors (View.createArrayBufferObject colors), colors.length
-						mesh.setVertexIndex (View.createIndexArrayBufferObject indices), indices.length
+						mesh.setVertexIndex (View.createElementArrayBufferObject indices), indices.length

 						View.addGeometry mesh
 					else
@@ -22,9 +22,9 @@
 			unless err
 				Model.Trianglesplane.get(width,(err, vertices, indices) ->
 					unless err
-						trianglesplane = new Trianlgesplane fragmentShader, vertexShader
-						trianglesplane.setNormalVertices vertices, verticesWidth
-						trianglesplane.setVertexIndex (View.createIndexArrayBufferObject indices), indices.length
+						trianglesplane = new Trianglesplane fragmentShader, vertexShader
+						trianglesplane.setNormalVertices vertices, width
+						trianglesplane.setVertexIndex (View.createElementArrayBufferObject indices), indices.length

 						View.addGeometry trianglesplane
 					else


center map on poland when no geolocation is provided on new relic

--- app/assets/javascripts/profile/location.coffee
+++ app/assets/javascripts/profile/location.coffee
@@ -34,23 +34,25 @@

     map: -> map

-    zoom_at: (lat, lng) ->
+    zoom_at: (lat, lng, zoom = 17) ->
       if window.map
         window.map.setCenter(lat, lng)
+        console.log(zoom)
+        window.map.map.setZoom(zoom)
       else
         window.map = new GMaps
           div: '#map_canvas'
           width: 340
           height: 340
-          zoom: 17
+          zoom: zoom
           lat: lat
           lng: lng
           mapTypeId: google.maps.MapTypeId.HYBRID

-    auto_zoom: ->
+    auto_zoom: (zoom = 17) ->
       latitude = $('#relic_latitude').val().toNumber()
       longitude = $('#relic_longitude').val().toNumber()
-      this.zoom_at(latitude, longitude)
+      this.zoom_at(latitude, longitude, zoom)
       map.removeMarkers()
       this.circle_marker(latitude, longitude)

@@ -123,11 +125,12 @@
   else
     $('#relic_latitude').val 52.4118436
     $('#relic_longitude').val 19.0984013
+    $('#map_canvas').auto_zoom(6)
     try
       navigator.geolocation.getCurrentPosition (pos) ->
         $('#relic_latitude').val pos.coords.latitude
         $('#relic_longitude').val pos.coords.longitude
-        $('#map_canvas').auto_zoom()
+        $('#map_canvas').auto_zoom(17)

 jQuery.initializer 'section.edit.location', ->
   $('#relic_place_id').select2()


focus method for widget class

focus method for widget class

--- app/assets/javascripts/quadro/widget.js.coffee
+++ app/assets/javascripts/quadro/widget.js.coffee
@@ -5,6 +5,7 @@
     @type = @element.data 'type'
     @page = @element.data 'page'
     @airmode = true
+    @focus = false
     @changed = false
     @summernote()
     return
@@ -20,6 +21,8 @@
         theme: 'monokai'
       onChange: =>
         @setChanged()
+      onfocus: =>
+        @setFocus()
         return
     return

@@ -89,6 +92,10 @@
     @content = $(".summernote[data-name=#{@name}]").code()
     return

+  setFocus: ->
+    @focus = true
+    return
+
   setChanged: ->
     @changed = true
     $('#quadro-actions').show()


[#34403883] Don't launch the edit modal if the click is on a link.

--- app/assets/javascripts/tandem/resources.coffee
+++ app/assets/javascripts/tandem/resources.coffee
@@ -11,7 +11,8 @@
     'mouseleave': ->
       $('div.tandem_toolbar', $(this)).fadeOut('slow')

-    'click': ->
-      $.fn.colorbox
-        href: $('a.tandem_edit_resource', $(this)).attr('href')
-      return false
+    'click': (event) ->
+      if event.target.tagName != 'A'
+        $.fn.colorbox
+          href: $('a.tandem_edit_resource', $(this)).attr('href')
+        return false


Fix load attributes

Fix load attributes

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -114,6 +114,7 @@
     loadValues: =>
         return @rs[@scope.resource].listValues(@scope.projectId, @scope.type).then (values) =>
             if values.length
+                @scope.values = values
                 @scope.maxValueOrder = _.maxBy(values, "order").order
             return values


[Backport] Fixing tasks bulk creation

[Backport] Fixing tasks bulk creation

--- app/coffee/modules/taskboard/lightboxes.coffee
+++ app/coffee/modules/taskboard/lightboxes.coffee
@@ -212,7 +212,7 @@

             promise = $rs.tasks.bulkCreate(projectId, sprintId, usId, data)
             promise.then (result) ->
-                result =  _.map(result, (x) => $model.make_model('userstories', x))
+                result =  _.map(result, (x) => $model.make_model('tasks', x))
                 currentLoading.finish()
                 $rootscope.$broadcast("taskform:bulk:success", result)
                 lightboxService.close($el)


Add and remove class "modal-open" to/from body

When the modal is too long it adds an additional scrollbar.
Bootstrap fixes this by adding 'modal-open' to the body.
We can do this too!
http://getbootstrap.com/javascript/#modals-usage

--- app/scripts/components/BsModalComponent.coffee
+++ app/scripts/components/BsModalComponent.coffee
@@ -32,9 +32,11 @@
             @show()

     becameVisible: ->
+        Em.$('body').addClass('modal-open')
         @appendBackdrop() if @get("backdrop")

     becameHidden: ->
+        Em.$('body').removeClass('modal-open')
         @_backdrop.remove() if @_backdrop

     appendBackdrop: ->
@@ -67,6 +69,7 @@

     #Invoked automatically by ember when the view is destroyed, giving us a chance to perform cleanups
     willDestroyElement: ->
+        Em.$('body').removeClass('modal-open')
         @removeHandlers()
         name = @get('name')
         name?= @get('elementId')
@@ -168,4 +171,4 @@
 Ember.Application.initializer
     name: 'bs-modal'
     initialize: (container, application) ->
-        container.register 'component:bs-modal', Bootstrap.BsModalComponent+        container.register 'component:bs-modal', Bootstrap.BsModalComponent