flesh out client demo a bit (still WIP)

flesh out client demo a bit (still WIP)

--- client/index.coffee
+++ client/index.coffee
@@ -1,19 +1,114 @@
-Rx = require('rxjs')
 pkg = require('./package.json')
-
+_ = require('lodash')
 React = require('react')
 R = React.DOM
+Rx = require('rx')
+#Rxdom = require('rx-dom') # may not need

-# TODO
-# - wire up websocket to vert.x
-# - consume David's event stream
-# - use Rx to throttle it and do rollups
-# - chart it
+window.app_root = app_root =
+  ns:
+    Rx: Rx
+    #Rxdom: Rxdom
+    React: React
+  config:
+    pkg: pkg
+    rest_api:
+      sources: "/api/metrics/sources"
+      requests: "/api/metrics/meters/requests"
+      responseTimes: "/api/metrics/histograms/responseTimes"
+    ws_api:
+     requests: "/streams/meters/requests"
+     responseTimes: "/streams/metrics/histograms/responseTimes"
+  events:
+    ui: new Rx.Subject()
+    requests: new Rx.Subject()
+    responseTimes: new Rx.Subject()
+  subscriptions:
+    # keep track for disposing them later: off/on
+    requests_ws: null
+    responseTimes_ws: null
+  ui_root: null

-# see
-# http://neugierig.org/software/blog/2014/02/react-jsx-coffeescript.html
-# for notes on using React directly in coffeescript rather than via jsx
-component = R.h1 null,
-  pkg.name
-  " a work in progress brought to you by Tavis and David!"
-React.renderComponent(component, document.body);
+wsurl = (path) ->
+  host = document.location.hostname
+  wsport = "8080" #document.location.port
+  "ws://#{host}:#{wsport}#{path}"
+
+observeWebSocket = (url) ->
+  # or just use
+  # https://github.com/Reactive-Extensions/RxJS-DOM/tree/master/doc#rxdomfromwebsocketurl-protocol-observeroronnext
+  # but I'm including this as an example of how to create a bidirectional subject
+  if url[0] == "/"
+    url = wsurl(url)
+  ws = new WebSocket(url)
+  observable = Rx.Observable.create (obs) ->
+    ws.onmessage = obs.onNext.bind(obs)
+    ws.onerror = obs.onError.bind(obs)
+    ws.onclose = obs.onCompleted.bind(obs)
+    # Return way to unsubscribe
+    -> ws.close()
+  observer = Rx.Observer.create (data) ->
+    if ws.readyState == WebSocket.OPEN
+      ws.send(data)
+  Rx.Subject.create(observer, observable)
+
+RequestsWidget = React.createClass
+  getInitialState: ->
+    '1m':  '-'
+    '5m':  '-'
+    '15m': '-'
+    count: '-'
+    mean:  '-'
+
+  componentWillMount: ->
+    app_root.events.requests.subscribe(@setState.bind(@))
+
+  render: ->
+      (R.div {}, [
+        (R.h2 {}, "Vert.x Request Metrics"),
+        '1m: ' + @state['1m'],
+        R.br {},
+        '5m: ' + @state['5m'],
+        R.br {},
+        '15m: ' + @state['15m'],
+        R.br {},
+        'count: ' + @state.count,
+        R.br {},
+        'mean: ' + @state.mean,
+        R.br {}
+        ])
+
+init_ui = ->
+  component = RequestsWidget({})
+  app_root.ui_root = React.renderComponent(component, document.body);
+
+connect_metrics_ws_to_sub = (url, sub) ->
+  observeWebSocket(url)
+    .map((ev) -> JSON.parse(ev.data))
+    .map((d) -> _.mapValues(d, (v) -> v.toFixed(2)))
+    .subscribe((e) -> sub.onNext(e))
+
+init_websockets = ->
+  url = (endpoint_name) -> app_root.config.ws_api[endpoint_name]
+  evs = app_root.events
+  app_root.subscriptions =
+    requests_ws: connect_metrics_ws_to_sub(url("requests"), evs.requests)
+    responseTimes_ws: connect_metrics_ws_to_sub(url("responseTimes"), evs.responseTimes)
+
+mouse_move_demo = ->
+  Rx.Observable.fromEvent(document, 'mousemove')
+    .distinctUntilChanged()
+    .sample(250)
+    .subscribe (e) ->
+      #console.log("mouse : #{e}")
+      console.log("mouse x: #{e.clientX} y: #{e.clientX}")
+
+onload = ->
+  init_ui()
+  mouse_move_demo()
+  init_websockets()
+  app_root.events.requests
+    .sample(3000)
+    .subscribe (e) -> console.log(e)
+
+window.addEventListener("load", onload, false)


Age module

Age module


Add description and improve user name handling in GaussBot

--- examples/GaussBot.coffee
+++ examples/GaussBot.coffee
@@ -10,7 +10,7 @@
   help: (msg) ->
     msg.reply
       text: """
-        /1a2b - start a 1A2B game
+        /1a2b - start a 1A2B game (https://en.wikipedia.org/wiki/Bulls_and_Cows)
         /cancel - cancel any ongoing games
         /help - about this bot
       """
@@ -30,8 +30,10 @@
       text = String(msg.text).trim()
       if text == randomNumber
         delete sessions[msg.chat.id]
+
+        name = if msg.from.username then "#{msg.from.username}" else msg.from.first_name
         msg.reply
-          text: "Congratulations, @#{msg.from.username || msg.from.first_name}! The answer is #{randomNumber}. Start a new game by sending /1a2b."
+          text: "Congratulations, #{name}! The answer is #{randomNumber}. Start a new game by sending /1a2b."
       else if text?.length == 4 && text.match(/^[0-9]{4}$/)
         if text.match(/([0-9]).*\1/)
           msg.reply
@@ -50,7 +52,7 @@
               force_reply: true

     msg.reply
-      text: "A new random number has been generated! Reply to me the number you guess ┏ (゜ω゜)=☞"
+      text: "A new random 4-digital number has been generated! Reply to me the number you guess ┏ (゜ω゜)=☞"
       reply_markup:
         force_reply: true


switch end with stop and use delete action

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -112,7 +112,7 @@
     listeners =
       "loc": @onLive
       "att": @onAttitude
-      "end": @onMissionEnd
+      "stop": @onMissionDelete
       "mode": @updateVehicleMessage
       "arm": @updateVehicleMessage

@@ -172,11 +172,6 @@
     @scope.$apply () =>
       key = @vehicleKey(data.missionId)
       delete @scope.vehicleMarkers[key]
-
-  onMissionEnd: (data) =>
-    vehicleKey = @vehicleKey(data.missionId)
-    delete @scope.vehicleMarkers[vehicleKey]
-    delete @scope.vehiclePaths[vehicleKey]

   # Zoom to vehicle, if it is different from the previous targeted vehicle
   zoomToVehicle: (vehicle) =>


prefer `is` over ==

prefer `is` over ==

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -105,7 +105,7 @@
     isExcludedAtMinimum: (exercises) ->
       excluded = _.filter _.pluck(exercises, 'id'),
         _.bind(@.exports.isExerciseExcluded, @)
-      exercises.length - excluded.length == 5
+      (exercises.length - excluded.length) is 5

     hasUnsavedExclusions: ->
       not _.isEmpty @_unsavedExerciseExclusions


Tags "name" field now only contains name

Not the previous name+description.  Additionally the name is often
blank with only the description present

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -8,6 +8,9 @@
   TEKS: 'teks'
   LO: ['lo', 'aplo']
   GENERIC: 'generic'
+
+getTagName = (tag) ->
+  [tag.name, tag.description].join(' ')

 getImportantTags = (tags) ->
   obj =
@@ -21,7 +24,7 @@
       tagArr.push(tag.id)
       memo.tagString = tagArr.join(" / ")
     else if (_.include(EXERCISE_TAGS.LO, tag.type))
-      memo.lo = tag.name
+      memo.lo = getTagName(tag)
       memo.section = tag.chapter_section
     memo
   , obj)
@@ -73,13 +76,13 @@
     getTeksString: (exercise_id) ->
       tags = @_exerciseCache[exercise_id].tags
       teksTags = _.where(tags, {type: EXERCISE_TAGS.TEKS})
-      _.pluck(teksTags, 'name').join(" / ")
+      _.map(teksTags, getTagName).join(" / ")

     getContent: (exercise_id) ->
       @_exerciseCache[exercise_id].content.questions[0].stem_html

     getTagContent: (tag) ->
-      content = if tag.name then tag.name else tag.id
+      content = getTagName(tag) or tag.id
       isLO = _.include(EXERCISE_TAGS.LO, tag.type)
       {content, isLO}


Only show fingerprint of SSH keys

Only show fingerprint of SSH keys

--- src/smc-webapp/widget-ssh-key-adder/main.cjsx
+++ src/smc-webapp/widget-ssh-key-adder/main.cjsx
@@ -130,7 +130,7 @@
                     />
                 </FormGroup>
             </form>
-

+

                 <Button
                     bsStyle  = 'success'
                     onClick  = {@submit_form}
@@ -143,8 +143,8 @@
         </Panel>

 AddKeyError = ({mesg}) ->
-    <Alert bsStyle='warning'>
-        mesg
+    <Alert bsStyle='danger'>
+        {mesg}
     </Alert>

 OneSSHKey = rclass
@@ -161,9 +161,9 @@
     render: ->
         <Well style={@props.style}>
             <Row>
-                <Col md=9>
+                <Col md=9 style={overflowX:'scroll'}>

{@props.ssh_key.get('title')}

-                    {@props.ssh_key.get('value')}

+                    Fingerprint: {@props.ssh_key.get('fingerprint')}

                     Added on {new Date(@props.ssh_key.get('last_use_date')).toLocaleDateString()}

                     Last used about <TimeAgo date={new Date(@props.ssh_key.get('last_use_date'))} />
                 </Col>
@@ -212,15 +212,13 @@

     getDefaultProps: ->
         placeholder1 = immutable.Map
-            value         : "ssh-rsa blah blah blah"
-            title          : "fake 1"
+            title          : "john@desktop"
             fingerprint   : '4c:c8:9f:65:01:3f:0a:6f:63:a2:77:d4:8a:59:8d:92'
             creation_date : '1995-12-17T03:24:00'
             last_use_date : '2012-12-17T03:24:00'

         placeholder2 = immutable.Map
-            value         : "ssh-rsa Blah Blah Blah"
-            title          : "fake 2"
+            title          : "J3@London"
             fingerprint   : '19:a3:c3:8a:91:19:92:26:97:50:01:bd:f3:1d:36:65'
             creation_date : '2002-12-17T03:24:00'
             last_use_date : '2016-12-17T03:24:00'
@@ -229,5 +227,5 @@

     render: ->
         <Panel>
-            {(<OneSSHKey ssh_key={ssh_key} key={ssh_key.get('value')} /> for ssh_key in @props.ssh_keys.toArray())}
+            {(<OneSSHKey ssh_key={ssh_key} key={ssh_key.get('fingerprint')} /> for ssh_key in @props.ssh_keys.toArray())}
         </Panel>


remove deployer superclass

remove deployer superclass

--- lib/deployers/deployer.coffee
+++ lib/deployers/deployer.coffee
@@ -1,33 +0,0 @@
-require 'colors'
-path = require 'path'
-
-class Deployer
-
-  constructor: ->
-    @name = 'deployer'
-
-    # optional global config
-    # - target: folder to deploy (default process.cwd())
-    # - before: path to before hook script
-    # - after: path to after hook script
-
-    @debug =
-      log: (m) -> console.log("#{m}".grey)
-      write: (m) -> process.stdout.write("#{m}".grey)
-
-  configure: (slug, data) ->
-    @config = data[slug]
-    @payload = if @config.target
-      path.join(@path, @config.target)
-    else
-      process.cwd()
-
-  deploy: (cb) ->
-    console.error('make sure you have defined a deploy method'.red)
-    cb()
-
-  # this is a method used for testing to ensure commands
-  # are being parsed correctly
-  mock_deploy: (cb) -> cb()
-
-module.exports = Deployer


Spacing now depends on the longest logger name


modify title

modify title

--- snippets/core.BridleNSIS.cson
+++ snippets/core.BridleNSIS.cson
@@ -1,192 +1,192 @@
 '.source.nsis.bridle':
-  'b:Abort()':
+  'Abort()':
     'prefix': 'Abort()'
     'body': 'Abort("${1:user_message}")'
-  'b:BringToFront()':
+  'BringToFront()':
     'prefix': 'BringToFront()'
     'body': 'BringToFront()'
-  'b:Call()':
+  'Call()':
     'prefix': 'Call()'
     'body': 'Call("${1:name}")'
-  'b:CallInstDLL()':
+  'CallInstDLL()':
     'prefix': 'CallInstDLL()'
     'body': 'CallInstDLL("${1:dll_file}", "${2:function_name}"'
-  'b:ClearErrors()':
+  'ClearErrors()':
     'prefix': 'ClearErrors()'
     'body': 'ClearErrors()'
-  'b:Copy()':
+  'Copy()':
     'prefix': 'Copy()'
     'body': 'Copy("${1:file}", "${2:target}")'
-  'b:CreateDirectory()':
+  'CreateDirectory()':
     'prefix': 'CreateDirectory()'
     'body': 'CreateDirectory("${1:path_to_create}")'
-  'b:CreateFont()':
+  'CreateFont()':
     'prefix': 'CreateFont()'
     'body': 'CreateFont("${1:face_name}",${2: "${3:height}"${4:, "${5:weight}"${6:, ${7:/ITALIC}${8:, ${9:/UNDERLINE}${10:, ${11:/STRIKE}}}}}})'
-  'b:CreateShortCut()':
+  'CreateShortCut()':
     'prefix': 'CreateShortCut()'
     'body': 'CreateShortCut("${1:link}.lnk", "${2:target_file}"${3:, "${4:parameters}"${5:, "${6:icon_file}"${7:, ${8:icon_index_number}${9:, ${10:start_options}${11:, ${12:keyboard_shortcut}${13:, "${14:description}"}}}}}}})'
-  'b:Delete()':
+  'Delete()':
     'prefix': 'Delete()'
     'body': 'Delete("${1:source}"${2:, "${3:rebootok}"})'
-  'b:DeleteINISec()':
+  'DeleteINISec()':
     'prefix': 'DeleteINISec()'
     'body': 'DeleteINISec("${1:ini_filename}", "${2:section_name}")'
-  'b:DeleteINIStr()':
+  'DeleteINIStr()':
     'prefix': 'DeleteINIStr()'
     'body': 'DeleteINIStr("${1:ini_filename}", "${2:section_name}", "${3:str_name}")'
-  'b:DeleteRegKey()':
+  'DeleteRegKey()':
     'prefix': 'DeleteRegKey()'
     'body': 'DeleteRegKey("${1:rootkey}", "${2:subkey}"}${3:, "${4:options}"})'
-  'b:DeleteRegValue()':
+  'DeleteRegValue()':
     'prefix': 'DeleteRegValue()'
     'body': 'DeleteRegValue("${1:root_key}", "${2:subkey}", "${3:key_name}")'
-  'b:DetailPrint()':
+  'DetailPrint()':
     'prefix': 'DetailPrint()'
     'body': 'DetailPrint("${1:user_message}")'
-  'b:Do Until..Loop':
+  'Do Until..Loop':
     'prefix': 'Do Until..Loop'
     'body': """
       Do Until ${1:condition}
         ${4:# your code here}
       Loop
     """
-  'b:Do While..Loop':
+  'Do While..Loop':
     'prefix': 'Do While..Loop'
     'body': """
       Do While ${1:condition}
         ${4:# your code here}
       Loop
     """
-  'b:EnableWindow()':
+  'EnableWindow()':
     'prefix': 'EnableWindow()'
     'body': 'EnableWindow(${1:hwnd}, ${2:value})'
-  'b:EnumRegKey()':
+  'EnumRegKey()':
     'prefix': 'EnumRegKey()'
     'body': 'EnumRegKey("${1:root_key}", "${2:subkey}", "${3:index}")'
-  'b:EnumRegValue()':
+  'EnumRegValue()':
     'prefix': 'EnumRegValue()'
     'body': 'EnumRegValue("${1:root_key}", "${2:subkey}", "${3:index}")'
-  'b:Exch()':
+  'Exch()':
     'prefix': 'Exch()'
     'body': 'Exch(${1:user_var})'
-  'b:Exec()':
+  'Exec()':
     'prefix': 'Exec()'
     'body': 'Exec("${1:command}")'
-  'b:ExecShell()':
+  'ExecShell()':
     'prefix': 'ExecShell()'
     'body': 'ExecShell("${1:action}", "${2:command}"${3:, ${4:parameter}})'
-  'b:ExecWait()':
+  'ExecWait()':
     'prefix': 'ExecWait()'
     'body': 'ExecWait("${1:command}")'
-  'b:ExpandEnvStrings()':
+  'ExpandEnvStrings()':
     'prefix': 'ExpandEnvStrings()'
     'body': 'ExpandEnvStrings("${1:string}")'
-  'b:File()':
+  'File()':
     'prefix': 'File()'
     'body': 'File("${1:file}"${2:, "${3:options}"${4:, "${5:outpath}"}})'
-  'b:FileClose()':
+  'FileClose()':
     'prefix': 'FileClose()'
     'body': 'FileClose(${1:handle})'
-  'b:FileCopy()':
+  'FileCopy()':
     'prefix': 'FileCopy()'
     'body': 'FileCopy("${1:file}", "${2:target}")'
-  'b:FileDelete()':
+  'FileDelete()':
     'prefix': 'FileDelete()'
     'body': 'FileDelete("${1:source}"${2:, "${3:rebootok}"})'
-  'b:FileOpen()':
+  'FileOpen()':
     'prefix': 'FileOpen()'
     'body': 'FileOpen("${1:filename}", ${2:open_mode})'
-  'b:FileRead()':
+  'FileRead()':
     'prefix': 'FileRead()'
     'body': 'FileRead(${1:handle}, ${2:maxlen})'
-  'b:FileReadByte()':
+  'FileReadByte()':
     'prefix': 'FileReadByte()'
     'body': 'FileReadByte(${1:handle}, ${2:maxlen})'
-  'b:FileReadUTF16LE()':
+  'FileReadUTF16LE()':
     'prefix': 'FileReadUTF16LE()'
     'body': 'FileReadUTF16LE(${1:handle}, ${2:maxlen})'
-  'b:FileReadWord()':
+  'FileReadWord()':
     'prefix': 'FileReadWord()'
     'body': 'FileReadWord(${1:handle}, ${2:maxlen})'
-  'b:FileRename()':
+  'FileRename()':
     'prefix': 'FileRename()'
     'body': 'FileRename("${1:source}", "${2:target}"${3:, "${4:options}"}})'
-  'b:FileSeek()':
+  'FileSeek()':
     'prefix': 'FileSeek()'
     'body': 'FileSeek(${1:handle}, ${2:offset}${3:, ${4:mode}${5:, ${6:user_var}}})'
-  'b:FileWrite()':
+  'FileWrite()':
     'prefix': 'FileWrite()'
     'body': 'FileWrite(${1:handle})'
-  'b:FileWriteByte()':
+  'FileWriteByte()':
     'prefix': 'FileWriteByte()'
     'body': 'FileWriteByte(${1:handle}, "${2:string}")'
-  'b:FileWriteUTF16LE()':
+  'FileWriteUTF16LE()':
     'prefix': 'FileWriteUTF16LE()'
     'body': 'FileWriteUTF16LE(${1:handle}, "${2:string}")'
-  'b:FileWriteWord()':
+  'FileWriteWord()':
     'prefix': 'FileWriteWord()'
     'body': 'FileWriteWord(${1:handle}, "${2:string}")'
-  'b:FindClose()':
+  'FindClose()':
     'prefix': 'FindClose()'
     'body': 'FindClose(${1:handle})'
-  'b:FindNext()':
+  'FindNext()':
     'prefix': 'FindNext()'
     'body': 'FindNext(${1:handle})'
-  'b:FindWindow()':
+  'FindWindow()':
     'prefix': 'FindWindow()'
     'body': 'FindWindow("${1:windowclass}", "${2:windowtitle}"${3:, ${4:windowparent}${5:, ${6:childafter}}})'
-  'b:FlushINI()':
+  'FlushINI()':
     'prefix': 'FlushINI()'
     'body': 'FlushINI("${1:ini_filename}")'
-  'b:Function()':
+  'Function()':
     'prefix': 'Function()'
     'body': """
       Function ${1:function_name}(${2:parameters})
         ${5:# your code here}
       FunctionEnd
     """
-  'b:GetCurInstType()':
+  'GetCurInstType()':
     'prefix': 'GetCurInstType()'
     'body': 'GetCurInstType()'
-  'b:GetCurrentAddress()':
+  'GetCurrentAddress()':
     'prefix': 'GetCurrentAddress()'
     'body': 'GetCurrentAddress()'
-  'b:GetDlgItem()':
+  'GetDlgItem()':
     'prefix': 'GetDlgItem()'
     'body': 'GetDlgItem(${1:dialog}, ${2:item_id})'
-  'b:GetErrorLevel()':
+  'GetErrorLevel()':
     'prefix': 'GetErrorLevel()'
     'body': 'GetErrorLevel()'
-  'b:GetFullPathName()':
+  'GetFullPathName()':
     'prefix': 'GetFullPathName()'
     'body': 'GetFullPathName("${1:path}"${2:, "${3:options}"})'
-  'b:GetFunctionAddress()':
+  'GetFunctionAddress()':
     'prefix': 'GetFunctionAddress()'
     'body': 'GetCurrentAddress("${1:function_name}")'
-  'b:GetInstDirError()':
+  'GetInstDirError()':
     'prefix': 'GetInstDirError()'
     'body': 'GetInstDirError()'
-  'b:GetLabelAddress()':
+  'GetLabelAddress()':
     'prefix': 'GetLabelAddress()'
     'body': 'GetLabelAddress("${1:label}")'
-  'b:GetTempFileName()':
+  'GetTempFileName()':
     'prefix': 'GetTempFileName()'
     'body': 'GetTempFileName(${1:\$${2:base_dir}})'
-  'b:Goto()':
+  'Goto()':
     'prefix': 'Goto()'
     'body': 'Goto(${1:name})'
-  'b:HideWindow()':
+  'HideWindow()':
     'prefix': 'HideWindow()'
     'body': 'HideWindow()'
-  'b:If Not':
+  'If Not':
     'prefix': 'If Not'
     'body': """
       If Not ${1:condition}
         ${4:# your code here}
       EndIf
     """
-  'b:If Not..Else':
+  'If Not..Else':
     'prefix': 'If Not..Else'
     'body': """
       If Not ${1:condition}
@@ -195,7 +195,7 @@
         ${5:# your code here}
       EndIf
     """
-  'b:If Not..ElseIf':
+  'If Not..ElseIf':
     'prefix': 'If Not..ElseIf'
     'body': """
       If Not ${1:condition}
@@ -204,14 +204,14 @@
         ${5:# your code here}
       EndIf
     """
-  'b:If':
+  'If':
     'prefix': 'If'
     'body': """
       If ${1:condition}
         ${4:# your code here}
       EndIf
     """
-  'b:If..Else':
+  'If..Else':
     'prefix': 'If..Else'
     'body': """
       If ${1:condition}
@@ -220,7 +220,7 @@
         ${5:# your code here}
       EndIf
     """
-  'b:If..ElseIf':
+  'If..ElseIf':
     'prefix': 'If..ElseIf'
     'body': """
       If ${1:condition}
@@ -229,288 +229,288 @@
         ${5:# your code here}
       EndIf
     """
-  'b:InitPluginsDir()':
+  'InitPluginsDir()':
     'prefix': 'InitPluginsDir()'
     'body': 'InitPluginsDir()'
-  'b:InstTypeGetText()':
+  'InstTypeGetText()':
     'prefix': 'InstTypeGetText()'
     'body': 'InstTypeGetText(${1:inst_type_idx})'
-  'b:InstTypeSetText()':
+  'InstTypeSetText()':
     'prefix': 'InstTypeSetText()'
     'body': 'InstTypeSetText(${1:inst_type_idx}, "${2:text}")'
-  'b:IntCmp()':
+  'IntCmp()':
     'prefix': 'IntCmp()'
     'body': 'IntCmp(${1:val1}, ${2:val2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}${6:, ${7:jump_if_val1_more}}})'
-  'b:IntFmt()':
+  'IntFmt()':
     'prefix': 'IntFmt()'
     'body': 'IntFmt(${1:format}, ${2:numberstring})'
-  'b:IntOp()':
+  'IntOp()':
     'prefix': 'IntOp()'
     'body': 'IntOp(${1:value1}, ${2:op}, ${3:value2})'
-  'b:LangString()':
+  'LangString()':
     'prefix': 'LangString()'
     'body': 'LangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:string}")'
-  'b:LicenseLangString()':
+  'LicenseLangString()':
     'prefix': 'LicenseLangString()'
     'body': 'LicenseLangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:license_path}")'
-  'b:LoadLanguageFile()':
+  'LoadLanguageFile()':
     'prefix': 'LoadLanguageFile()'
     'body': 'LoadLanguageFile("${1:language_file_nlf}")'
-  'b:LockWindow()':
+  'LockWindow()':
     'prefix': 'LockWindow()'
     'body': 'LockWindow(${1:value})'
-  'b:LockWindow(off)':
+  'LockWindow(off)':
     'prefix': 'LockWindow(off)'
     'body': 'LockWindow(off)'
-  'b:LockWindow(on)':
+  'LockWindow(on)':
     'prefix': 'LockWindow(on)'
     'body': 'LockWindow(on)'
-  'b:LogSet()':
+  'LogSet()':
     'prefix': 'LogSet()'
     'body': 'LogSet(${1:value})'
-  'b:LogSet(off)':
+  'LogSet(off)':
     'prefix': 'LogSet(off)'
     'body': 'LogSet(off)'
-  'b:LogSet(on)':
+  'LogSet(on)':
     'prefix': 'LogSet(on)'
     'body': 'LogSet(on)'
-  'b:LogText()':
+  'LogText()':
     'prefix': 'LogText()'
     'body': 'LogText("${1:text}")'
-  'b:MsgBox()':
+  'MsgBox()':
     'prefix': 'MsgBox()'
     'body': 'MsgBox("${1:buttons}", "${2:message}"${3:, ${4:options}${5:, ${6:sd}}})'
-  'b:MsgBox(ABORTRETRYIGNORE)':
+  'MsgBox(ABORTRETRYIGNORE)':
     'prefix': 'MsgBox(ABORTRETRYIGNORE)'
     'body': 'MsgBox("ABORTRETRYIGNORE", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})'
-  'b:MsgBox(OK)':
+  'MsgBox(OK)':
     'prefix': 'MsgBox(OK)'
     'body': 'MsgBox("OK", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})'
-  'b:MsgBox(OKCANCEL)':
+  'MsgBox(OKCANCEL)':
     'prefix': 'MsgBox(OKCANCEL)'
     'body': 'MsgBox("OKCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})'
-  'b:MsgBox(RETRYCANCEL)':
+  'MsgBox(RETRYCANCEL)':
     'prefix': 'MsgBox(RETRYCANCEL)'
     'body': 'MsgBox("RETRYCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})'
-  'b:MsgBox(YESNO)':
+  'MsgBox(YESNO)':
     'prefix': 'MsgBox(YESNO)'
     'body': 'MsgBox("YESNO", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})'
-  'b:MsgBox(YESNOCANCEL)':
+  'MsgBox(YESNOCANCEL)':
     'prefix': 'MsgBox(YESNOCANCEL)'
     'body': 'MsgBox("YESNOCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})'
-  'b:Pop()':
+  'Pop()':
     'prefix': 'Pop()'
     'body': '${1:user_var} = Pop()'
-  'b:Push()':
+  'Push()':
     'prefix': 'Push()'
     'body': 'Push(${1:string})'
-  'b:Quit()':
+  'Quit()':
     'prefix': 'Quit()'
     'body': 'Quit()'
-  'b:ReadEnvStr()':
+  'ReadEnvStr()':
     'prefix': 'ReadEnvStr()'
     'body': 'ReadEnvStr(${1:name})'
-  'b:ReadINIStr()':
+  'ReadINIStr()':
     'prefix': 'ReadINIStr()'
     'body': 'ReadINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}")'
-  'b:ReadRegDWORD()':
+  'ReadRegDWORD()':
     'prefix': 'ReadRegDWORD()'
     'body': 'ReadRegDWORD("${1:root_key}", "${2:sub_key}", "${3:name}")'
-  'b:ReadRegStr()':
+  'ReadRegStr()':
     'prefix': 'ReadRegStr()'
     'body': 'ReadRegStr("${1:root_key}", "${2:sub_key}", "${3:name}")'
-  'b:Reboot()':
+  'Reboot()':
     'prefix': 'Reboot()'
     'body': 'Reboot()'
-  'b:RegDLL()':
+  'RegDLL()':
     'prefix': 'RegDLL()'
     'body': 'RegDLL("${1:file}"${2:, ${3:options}})'
-  'b:Rename()':
+  'Rename()':
     'prefix': 'Rename()'
     'body': 'Rename("${1:source}", "${2:target}"${3:, "${4:options}"}})'
-  'b:ReserveFile()':
+  'ReserveFile()':
     'prefix': 'ReserveFile()'
     'body': 'ReserveFile("${1:file}"${2:, "${3:options}"}})'
-  'b:RMDir()':
+  'RMDir()':
     'prefix': 'RMDir()'
     'body': 'RMDir("${1:directory}"${2:, "${3:options}"}})'
-  'b:SearchPath()':
+  'SearchPath()':
     'prefix': 'SearchPath()'
     'body': 'SearchPath("${1:filename}")'
-  'b:SectionGetFlags()':
+  'SectionGetFlags()':
     'prefix': 'SectionGetFlags()'
     'body': 'SectionGetFlags(${1:section_index})'
-  'b:SectionGetInstTypes()':
+  'SectionGetInstTypes()':
     'prefix': 'SectionGetInstTypes()'
     'body': 'SectionGetInstTypes(${1:section_index})'
-  'b:SectionGetSize()':
+  'SectionGetSize()':
     'prefix': 'SectionGetSize()'
     'body': 'SectionGetSize(${1:section_index})'
-  'b:SectionSetFlags()':
+  'SectionSetFlags()':
     'prefix': 'SectionSetFlags()'
     'body': 'SectionSetFlags(${1:section_index}, ${2:inst_types})'
-  'b:SectionSetInstTypes()':
+  'SectionSetInstTypes()':
     'prefix': 'SectionSetInstTypes()'
     'body': 'SectionSetInstTypes(${1:section_index}, ${2:section_flags})'
-  'b:SectionSetSize()':
+  'SectionSetSize()':
     'prefix': 'SectionSetSize()'
     'body': 'SectionSetSize(${1:handle}, ${2:maxlen})'
-  'b:SectionSetText()':
+  'SectionSetText()':
     'prefix': 'SectionSetText()'
     'body': 'SectionSetText(${1:section_index}, "${2:section_text}")'
-  'b:SendMessage()':
+  'SendMessage()':
     'prefix': 'SendMessage()'
     'body': 'SendMessage(${1:hwnd}, ${2:msg}, ${3:wparam}, ${4:lparam}${5:, ${6:timeout_in_ms}})'
-  'b:SetAutoClose()':
+  'SetAutoClose()':
     'prefix': 'SetAutoClose()'
     'body': 'SetAutoClose(${1:value})'
-  'b:SetAutoClose(false)':
+  'SetAutoClose(false)':
     'prefix': 'SetAutoClose(false)'
     'body': 'SetAutoClose(false)'
-  'b:SetAutoClose(true)':
+  'SetAutoClose(true)':
     'prefix': 'SetAutoClose(true)'
     'body': 'SetAutoClose(true)'
-  'b:SetCtlColors()':
+  'SetCtlColors()':
     'prefix': 'SetCtlColors()'
     'body': 'SetCtlColors(${1:hwnd}${2:, ${3:branding}}, ${4:text_color}, ${5:bg_color})'
-  'b:SetCurInstType()':
+  'SetCurInstType()':
     'prefix': 'SetCurInstType()'
     'body': 'SetCurInstType(${1:inst_type_idx})'
-  'b:SetDetailsPrint()':
+  'SetDetailsPrint()':
     'prefix': 'SetDetailsPrint()'
     'body': 'SetDetailsPrint(${1:value})'
-  'b:SetDetailsPrint(both)':
+  'SetDetailsPrint(both)':
     'prefix': 'SetDetailsPrint(both)'
     'body': 'SetDetailsPrint(both)'
-  'b:SetDetailsPrint(lastused)':
+  'SetDetailsPrint(lastused)':
     'prefix': 'SetDetailsPrint(lastused)'
     'body': 'SetDetailsPrint(lastused)'
-  'b:SetDetailsPrint(listonly)':
+  'SetDetailsPrint(listonly)':
     'prefix': 'SetDetailsPrint(listonly)'
     'body': 'SetDetailsPrint(listonly)'
-  'b:SetDetailsPrint(none)':
+  'SetDetailsPrint(none)':
     'prefix': 'SetDetailsPrint(none)'
     'body': 'SetDetailsPrint(none)'
-  'b:SetDetailsPrint(textonly)':
+  'SetDetailsPrint(textonly)':
     'prefix': 'SetDetailsPrint(textonly)'
     'body': 'SetDetailsPrint(textonly)'
-  'b:SetDetailsView()':
+  'SetDetailsView()':
     'prefix': 'SetDetailsView()'
     'body': 'SetDetailsView(${1:value})'
-  'b:SetDetailsView(hide)':
+  'SetDetailsView(hide)':
     'prefix': 'SetDetailsView(hide)'
     'body': 'SetDetailsView(hide)'
-  'b:SetDetailsView(show)':
+  'SetDetailsView(show)':
     'prefix': 'SetDetailsView(show)'
     'body': 'SetDetailsView(show)'
-  'b:SetErrorLevel()':
+  'SetErrorLevel()':
     'prefix': 'SetErrorLevel()'
     'body': 'SetErrorLevel(${1:error_level})'
-  'b:SetErrorLevel(0)':
+  'SetErrorLevel(0)':
     'prefix': 'SetErrorLevel(0)'
     'body': 'SetErrorLevel(0)'
-  'b:SetErrorLevel(1)':
+  'SetErrorLevel(1)':
     'prefix': 'SetErrorLevel(1)'
     'body': 'SetErrorLevel(1)'
-  'b:SetErrorLevel(2)':
+  'SetErrorLevel(2)':
     'prefix': 'SetErrorLevel(2)'
     'body': 'SetErrorLevel(2)'
-  'b:SetErrors()':
+  'SetErrors()':
     'prefix': 'SetErrors()'
     'body': 'SetErrors()'
-  'b:SetFileAttributes()':
+  'SetFileAttributes()':
     'prefix': 'SetFileAttributes()'
     'body': 'SetFileAttributes("${1:filename}", ${2:attributes})'
-  'b:SetOutPath()':
+  'SetOutPath()':
     'prefix': 'SetOutPath()'
     'body': 'SetOutPath("${1:path}")'
-  'b:SetRebootFlag()':
+  'SetRebootFlag()':
     'prefix': 'SetRebootFlag()'
     'body': 'SetRebootFlag(${1:value})'
-  'b:SetRebootFlag(false)':
+  'SetRebootFlag(false)':
     'prefix': 'SetRebootFlag(false)'
     'body': 'SetRebootFlag(false)'
-  'b:SetRebootFlag(true)':
+  'SetRebootFlag(true)':
     'prefix': 'SetRebootFlag(true)'
     'body': 'SetRebootFlag(true)'
-  'b:SetRegView()':
+  'SetRegView()':
     'prefix': 'SetRegView()'
     'body': 'SetRegView(${1:value})'
-  'b:SetRegView(32)':
+  'SetRegView(32)':
     'prefix': 'SetRegView(32)'
     'body': 'SetRegView(32)'
-  'b:SetRegView(64)':
+  'SetRegView(64)':
     'prefix': 'SetRegView(64)'
     'body': 'SetRegView(64)'
-  'b:SetRegView(lastused)':
+  'SetRegView(lastused)':
     'prefix': 'SetRegView(lastused)'
     'body': 'SetRegView(lastused)'
-  'b:SetShellVarContext()':
+  'SetShellVarContext()':
     'prefix': 'SetShellVarContext()'
     'body': 'SetShellVarContext(${1:value})'
-  'b:SetShellVarContext(all)':
+  'SetShellVarContext(all)':
     'prefix': 'SetShellVarContext(all)'
     'body': 'SetShellVarContext(all)'
-  'b:SetShellVarContext(current)':
+  'SetShellVarContext(current)':
     'prefix': 'SetShellVarContext(current)'
     'body': 'SetShellVarContext(current)'
-  'b:SetSilent()':
+  'SetSilent()':
     'prefix': 'SetSilent()'
     'body': 'SetSilent(${1:value})'
-  'b:SetSilent(normal)':
+  'SetSilent(normal)':
     'prefix': 'SetSilent(normal)'
     'body': 'SetSilent(normal)'
-  'b:SetSilent(silent)':
+  'SetSilent(silent)':
     'prefix': 'SetSilent(silent)'
     'body': 'SetSilent(silent)'
-  'b:ShowWindow()':
+  'ShowWindow()':
     'prefix': 'ShowWindow()'
     'body': 'ShowWindow(${1:hwnd}, ${2:value})'
-  'b:Sleep()':
+  'Sleep()':
     'prefix': 'Sleep()'
     'body': 'Sleep(${1:sleeptime_in_ms})'
-  'b:StrCmp()':
+  'StrCmp()':
     'prefix': 'StrCmp()'
     'body': 'StrCmp(${1:str1}, ${2:str2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}})'
-  'b:StrCpy()':
+  'StrCpy()':
     'prefix': 'StrCpy()'
     'body': 'StrCpy(${1:str}${2:, ${3:maxlen}${4:, ${5:start_offset}}})'
-  'b:StrLen()':
+  'StrLen()':
     'prefix': 'StrLen()'
     'body': 'StrLen(${1:str})'
-  'b:UnRegDLL()':
+  'UnRegDLL()':
     'prefix': 'UnRegDLL()'
     'body': 'UnRegDLL("${1:dllfile}")'
-  'b:Var':
+  'Var':
     'prefix': 'Var'
     'body': 'Var ${1:user_vars}'
-  'b:WordFind()':
+  'WordFind()':
     'prefix': 'WordFind()'
     'body': 'WordFind("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"}${6:, "${7:center}"}})'
-  'b:WordFindS()':
+  'WordFindS()':
     'prefix': 'WordFindS()'
     'body': 'WordFindS("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"}${6:, "${7:center}"}})'
-  'b:WordReplace()':
+  'WordReplace()':
     'prefix': 'WordReplace()'
     'body': 'WordReplace("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")'
-  'b:WordReplaceS()':
+  'WordReplaceS()':
     'prefix': 'WordReplaceS()'
     'body': 'WordReplaceS("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")'
-  'b:WriteINIStr()':
+  'WriteINIStr()':
     'prefix': 'WriteINIStr()'
     'body': 'WriteINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}", "${4:value}")'
-  'b:WriteRegBin()':
+  'WriteRegBin()':
     'prefix': 'WriteRegBin()'
     'body': 'WriteRegBin("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value_data}")'
-  'b:WriteRegDWORD()':
+  'WriteRegDWORD()':
     'prefix': 'WriteRegDWORD()'
     'body': 'WriteRegDWORD("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")'
-  'b:WriteRegExpandStr()':
+  'WriteRegExpandStr()':
     'prefix': 'WriteRegExpandStr()'
     'body': 'WriteRegExpandStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")'
-  'b:WriteRegStr()':
+  'WriteRegStr()':
     'prefix': 'WriteRegStr()'
     'body': 'WriteRegStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")'
-  'b:WriteUninstaller()':
+  'WriteUninstaller()':
     'prefix': 'WriteUninstaller()'
     'body': 'WriteUninstaller("${1:path}.exe")'


@browser.show html

@browser.show html

--- src/atom/browser.coffee
+++ src/atom/browser.coffee
@@ -16,8 +16,12 @@

     true

-  show: ->
+  # innerHTML - Optional String to set as iframe's content.
+  show: (innerHTML=null) ->
     style = "width:100%;height:100%;background-color:#fff;border:none"
     @html = "<iframe src='#{@url}' style='#{style}'></iframe>"

     super
+
+    if innerHTML
+      @pane.find('iframe')[0].contentWindow.document.body.innerHTML = innerHTML


bug in item coffescript code

bug in item coffescript code

--- ZONE-watcher/app/assets/javascripts/items.js.coffee
+++ ZONE-watcher/app/assets/javascripts/items.js.coffee
@@ -16,10 +16,15 @@
         $('[class=item_container][sourceid="'+id+'"]').append(data)
         $('[class*=item_wait][sourceid="'+id+'"]').detach()

-(document).ready ->
- #Hide the favorite bar by default
-$(".row-favorite").hide()
- $(".row-list").hide()
+
+
+
+
+$(document).ready ->
+
+  #Hide the favorite bar by default
+  $(".row-favorite").hide()
+  $(".row-list").hide()

   $(".hideTag").hide();
   $(".btn-toolbar").hide();


Stop paginating when there are no more results.

--- client/results.coffee
+++ client/results.coffee
@@ -1,4 +1,7 @@
 searchLimitIncreasing = false
+
+currentSearchQueryCount = ->
+  (Session.get('currentSearchQueryCountPublications') or 0) + (Session.get('currentSearchQueryCountPeople') or 0)

 Deps.autorun ->
   # Every time search query is changed, we reset counts
@@ -36,7 +39,9 @@
   $(window).off 'scroll.results'

 increaseSearchLimit = (pageSize) ->
-  if not searchLimitIncreasing
+  if searchLimitIncreasing
+    return
+  if Session.get('currentSearchLimit') < currentSearchQueryCount()
     searchLimitIncreasing = true
     Session.set 'currentSearchLimit', (Session.get('currentSearchLimit') or 0) + pageSize

@@ -68,13 +73,13 @@
   Session.get 'currentSearchQueryCountPeople'

 Template.noResults.noResults = ->
-  Session.get('currentSearchQueryReady') and not Session.get('currentSearchQueryCountPublications') and not Session.get('currentSearchQueryCountPeople')
+  Session.get('currentSearchQueryReady') and not currentSearchQueryCount()

 Template.resultsLoad.loading = ->
   Session.get('currentSearchQueryLoading')

 Template.resultsLoad.more = ->
-  Session.get('currentSearchQueryReady') and Session.get('currentSearchLimit') < (Session.get('currentSearchQueryCountPublications') or Session.get('currentSearchQueryCountPeople') or 0)
+  Session.get('currentSearchQueryReady') and Session.get('currentSearchLimit') < currentSearchQueryCount()

 Template.resultsLoad.events =
   'click .load-more': (e, template) ->


Add totals to TSV statistics

Add totals to TSV statistics

--- client/stats.coffee
+++ client/stats.coffee
@@ -232,16 +232,23 @@
     stats =
       type: 'users'
       query: -> group: @group
-      datasets: Meteor.users.find({}, sort: username: 1).map (user) ->
+      datasets: [
+        fullname: 'ALL MESSAGES'
+        msgFilter: (msg) -> true
+      ].concat Meteor.users.find({}, sort: username: 1).map (user) ->
         username: user.username
         fullname: user.profile?.fullname
         email: user.emails?[0]?.address
         msgFilter: msgFilter user.username
     buildStats stats, t.data
-    rows = [['username', 'fullname', 'email'].concat stats.labels]
-    for dataset in stats.datasets
-      rows.push [dataset.username, dataset.fullname, dataset.email ? ''] \
-        .concat dataset.data
+    rows = [['Username', 'Fullname', 'Email', 'Total'].concat stats.labels]
+    for dataset in stats.datasets
+      rows.push [
+        dataset.username ? ''
+        dataset.fullname ? ''
+        dataset.email ? ''
+        dataset.data.reduce ((x, y) -> x+y), 0
+      ].concat dataset.data
     tsv =
       (for row in rows
         (for cell in row


Fix list constants in macros and inserting backspace in the editor

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1721,17 +1721,15 @@
     when 'string' then (tokenize JSON.stringify value)[0]
     when 'object'
       kind = Object.prototype.toString.call(value).slice(8, -1)
-      value
-      # switch kind
+      switch kind
         # when 'Date' then (jsNew 'Date', [+value])
-        # when 'RegExp' then (jsNew 'RegExp', [value.source])
-        # else
-          #TODO: rest of immutable
-          # if Immutable.Iterable.isIterable value
-          #   (jsCallMethod 'Immutable', 'List',
-          #     [(jsCallMethod 'JSON', 'parse', [JSON.stringify(value.toJS())])])
-          # else
-            # expressionCompile ctx, value
+        when 'RegExp' then (tokenize "#{value.source}")[0]
+        else
+          # TODO: rest of immutable
+          if Immutable.Iterable.isIterable value
+            concat [(tokenize "{")[0], (map constantToSource, value.toJS()), (tokenize "}")[0]]
+          else
+            value

 ms.macro = ms_macro = (ctx, call) ->
   hasName = requireName ctx, 'Name required to declare a new instance'


Added polymorphic empty

Added polymorphic empty

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -636,8 +636,8 @@
     macros[op] = cb mapping.to[i]

 unaryFnMapping =
-  from: '√ alert! not'.split ' '
-  to: 'Math.sqrt window.log !'.split ' '
+  from: '√ alert! not empty'.split ' '
+  to: 'Math.sqrt window.log ! $empty'.split ' '

 expandBuiltings unaryFnMapping, (to) ->
   (x) ->
@@ -766,6 +766,20 @@
     return xs.slice(from, xs.length - leave);
   }
   return $listSlice(from, $sequenceSize(xs) - leave - from, xs);
+};
+
+// temporary, will be replaced by typed 0-argument function
+var $empty = function (xs) {
+  if (typeof xs == 'string' || xs instanceof String) {
+    return "";
+  }
+  if (xs.unshift) {
+    return [];
+  }
+  if ('head' in xs) {
+    return $listize([]);
+  } // cases for other sequences
+  return {};
 };

 var $listSlice = function (from, n, xs) {


Proper hoisting based on graph, working now

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -403,8 +403,8 @@
   [readyWheres, hoistableWheres] = wheresWithHoisting whereList tokens
   if hoistableWheres.length > 0
     undefinedNames = []
-    for {names} in hoistableWheres
-      undefinedNames.push names...
+    for {missing} in hoistableWheres
+      undefinedNames.push (setToArray missing)...
     throw new Error "#{undefinedNames.join ', '} used but not defined"
   addToScopeAllNames readyWheres
   graphToWheres readyWheres
@@ -585,7 +585,7 @@
   [readyWheres, hoistableWheres] = wheresWithHoisting wheres
   addToScopeAllNames readyWheres
   wheresDef = (graphToWheres readyWheres).map(compileDef).join '\n'
-  bodyDef = compileImpl body, graphToWheres hoistableWheres
+  bodyDef = compileImpl body, hoistableWheres
   [wheresDef, bodyDef]

 unpairs = (pairs) ->
@@ -620,7 +620,7 @@
   valid = []
   for {missing, names} in graph
     # This def needs hoisting
-    if missing.length > 0
+    if missing.numDependencies > 0
       hoistableNames[n] = yes for n in names
       # So do all defs depending on it
       for name in names
@@ -685,7 +685,7 @@
     def: [pattern, def]
     set: dependencySet()
     names: findNames pattern
-    missing: []
+    missing: dependencySet()

   lookupByName = lookupTableForGraph graph

@@ -703,7 +703,7 @@
           addDependency child.set, name for name in parent.names unless child is parent
           addDependency deps, parent
         else if !node.label and !lookupIdentifier token, node
-          child.missing.push node.token
+          addDependency child.missing, node.token
   [graph, lookupByName]

 lookupTableForGraph = (graph) ->
@@ -735,6 +735,12 @@
   return if !set.dependencies[parentName]
   set.numDependencies -= 1
   delete set.dependencies[parentName]
+
+cloneDependencySet = (set) ->
+  clone = {}
+  for own parentName of set
+    clone[parentName] = true
+  clone

 compileDef = ([name, def]) ->
   if !def?
@@ -898,9 +904,9 @@
       {conds, preassigns} = constructCond precs
       [hoistedWheres, furtherHoistable] = hoistWheres hoistableWheres, assigns
       """#{control} (#{conds}) {
-           #{preassigns.concat(assigns).map(compileAssign).join '\n'}
-           #{hoistedWheres.map(compileDef).join '\n'}
-           return #{compileImpl result, furtherHoistable};
+          #{preassigns.concat(assigns).map(compileAssign).join '\n  '}
+          #{hoistedWheres.map(compileDef).join '\n  '}
+          return #{compileImpl result, furtherHoistable};
         }"""
       )
     mainCache ?= []
@@ -918,17 +924,40 @@
 compileName = (node) ->
   validIdentifier node.token

-hoistWheres = (possible, assigns) ->
-  defined = (n for [n, _] in assigns)
+hoistWheres = (hoistable, assigns) ->
+  defined = addAllTo dependencySet(), (n for [n, _] in assigns)
+  hoistedNames = dependencySet()
   hoisted = []
   notHoisted = []
-  for [pattern, def, missingNames] in possible
-    stillMissingNames = (name for name in missingNames when name not in defined)
-    if stillMissingNames.length == 0
-      hoisted.push [pattern, def]
+  for where in hoistable
+    {missing, names, def, set} = where
+    stillMissingNames = addAllTo dependencySet(), (name for name of missing when not defined[name])
+    if stillMissingNames.numDependencies == 0 and set.numDependencies == 0
+      hoisted.push def
+      addAllTo hoistedNames, names
     else
-      notHoisted.push [pattern, def, stillMissingNames]
+      notHoisted.push
+        def: def
+        names: names
+        missing: stillMissingNames
+        set: removeAll (cloneDependencySet set), setToArray hoistedNames
+
   [hoisted, notHoisted]
+
+addAllTo = (set, array) ->
+  for v in array
+    addDependency set, v
+  set
+
+removeAll = (set, array) ->
+  for v in array
+    removeDependency set, v
+  set
+
+setToArray = (set) ->
+  for n of set.dependencies
+    n
+

 toJsString = (token) ->
   "'#{token}'"


Fix the root() function

Fix the root() function

--- lib/psd/nodes/ancestry.coffee
+++ lib/psd/nodes/ancestry.coffee
@@ -2,7 +2,7 @@

 module.exports =
   root: ->
-    return @ if isRoot()
+    return @ if @isRoot()
     return @parent.root()

   isRoot: -> @depth() is 0
@@ -41,4 +41,4 @@

   path: (asArray = false) ->
     path = @ancestors().map((n) -> n.name).concat([@name])
-    if asArray then path else path.join('/')+    if asArray then path else path.join('/')


List dblclick fire only on TD target

List dblclick fire only on TD target

--- app/assets/javascripts/ab_admin/core/init.js.coffee
+++ app/assets/javascripts/ab_admin/core/init.js.coffee
@@ -43,9 +43,10 @@

   if window.fv?.list_dblclick
     $(document).on 'dblclick', '#list > tbody > tr', (e) ->
-      e.preventDefault()
-      unless $(e.target).closest('#list > tbody > tr > td').hasClass('list_adds')
-        $(this).find('td a.resource_id_link').toHref()
+      if e.target.tagName == 'TD'
+        e.preventDefault()
+        unless $(e.target).closest('#list > tbody > tr > td').hasClass('list_adds')
+          $(this).find('td a.resource_id_link').toHref()

   initFancySelect()
   initNestedFields()


don't call the api too soon

don't call the api too soon

--- app/assets/javascripts/lib/models/chart_list.coffee
+++ app/assets/javascripts/lib/models/chart_list.coffee
@@ -18,16 +18,17 @@
   # holder_id - id of the dom element that will hold the chart. If null then a
   #             new chart holder will be created
   # options   - hash with some options (default: {})
-  #             alternate - id of the chart to load if the first one fails.
-  #                         Watch out for loops!
-  #             force     - (re)load the chart entirely
-  #             header    - show or hide the chart header (default: true)
-  #             locked    - flag the chart as locked (default: false)
-  #             as_table  - render the chart as a table (default: false). Only
-  #                         some chart types have this feature!
+  #             alternate   - id of the chart to load if the first one fails.
+  #                           Watch out for loops!
+  #             force       - (re)load the chart entirely
+  #             header      - show or hide the chart header (default: true)
+  #             locked      - flag the chart as locked (default: false)
+  #             as_table    - render the chart as a table (default: false). Only
+  #                           some chart types have this feature!
   #             ignore_lock - don't overwrite the lock flag. This might be
   #                           needed on the initial render, where we want to
   #                           plot the charts and preserve the locks
+  #             wait        - don't fire the API request immediately
   #
   # Returns the newly created chart object or false if something went wrong
   load: (chart_id, holder_id = null, options = {}) =>
@@ -82,7 +83,7 @@
           new_chart.series.add(s)

         new_chart.update_buttons()
-        App.call_api()
+        App.call_api() unless options.wait
     @last()

   # Returns the chart held in a holder. This assumes a chart can be shown only
@@ -132,9 +133,13 @@
         @load(id, holder, {
           locked: locked,
           as_table: (format == 'T'),
-          ignore_lock: true # the initial render should ignore the lock check:
-        })                  # render the charts but don't remove the locks,
-                            # which is what `force: true` would do
+          # the initial render should ignore the lock check: render the charts
+          # but don't remove the locks, which is what `force: true` would do
+          ignore_lock: true,
+          # Load all of them before triggering the api request
+          wait: true
+        })
+    App.call_api()

   # adds a chart container, unless it is already in the DOM. Returns the
   # holder_id


make String::escapeEntities escape single and double quotes

--- scripts/utilities.coffee
+++ scripts/utilities.coffee
@@ -47,7 +47,7 @@
         else          'undefined'

 # Ugh, dirty hax. Why doesn't JavaScript come with this?
-String::escapeEntities   = -> $('<div>').text("#{this}").html()
+String::escapeEntities   = -> $('<div>').text("#{this}").html().replace('"', '"').replace("'", ''')
 String::unescapeEntities = -> $('<div>').html("#{this}").text()

 hideProperties = (obj, properties...) ->


ENHANCE: Add success alert messages on changing tutorial

--- src/app/projects/states/tutorials/tutorials.coffee
+++ src/app/projects/states/tutorials/tutorials.coffee
@@ -22,10 +22,17 @@
     Project.update(
       { id: $scope.project.project_id, tutorial_id: id }
       (project) ->
+        oldTutorialAbbr = $scope.unit.tutorialFromId($scope.project.tutorial_id)?.abbreviation
         $scope.project.tutorial_id = project.tutorial_id
         $scope.project.tutorial = $scope.unit.tutorialFromId( $scope.project.tutorial_id )
-        eventName = if id isnt -1 then "Changed tutorial" else "Withdrew from all tutorials"
-        analyticsService.event "Student Project View - Tutorials Tab", eventName
+        if id == -1
+          eventName = "Withdrew from all tutorials"
+          successMsg = "Withdrew from #{oldTutorialAbbr}"
+        else
+          eventName = "Changed tutorial"
+          successMsg = "Enrolled in #{$scope.project.tutorial.abbreviation}"
+        analyticsService.event("Student Project View - Tutorials Tab", eventName)
+        alertService.add("success", successMsg, 3000)
         projectService.updateGroups($scope.project) #can be removed from groups by changing labs
       (response) -> alertService.add("danger", response.data.error, 6000)
     )


Use results interpreter

Use results interpreter

--- src/coffee/controllers/questions_controller.coffee
+++ src/coffee/controllers/questions_controller.coffee
@@ -17,13 +17,7 @@
       page '/questions/results'

   results: ->
-    console.log 'helo!'
-    console.log SwanKiosk.Store.answers
-    {
-      tag: 'pre'
-      rawHtml: true
-      contents: JSON.stringify(SwanKiosk.Store.answers || {})
-    }
+    new SwanKiosk.Interpreters.Results SwanKiosk.Store.answers

   _selectOption: (value, event) ->
     $answer = $ event.target


move welcome message to initializng method

--- generators/app/index.coffee
+++ generators/app/index.coffee
@@ -8,6 +8,10 @@

   initializing : ->

+    @log yosay(
+      "Welcome to the amazing #{chalk.green('Phaser-Coffeescript-Gulp')} generator!"
+    )
+

   install : ->

@@ -17,10 +21,6 @@
   prompting : ->

     done = @async()
-
-    @log yosay(
-      "Welcome to the amazing #{chalk.green('Phaser-Coffeescript-Gulp')} generator!"
-    )

     @prompt [
       {


fix exit code when using --nodejs option

--- command.coffee
+++ command.coffee
@@ -346,10 +346,11 @@
   nodeArgs = opts.nodejs.split /\s+/
   args     = process.argv[1..]
   args.splice args.indexOf('--nodejs'), 2
-  spawn process.execPath, nodeArgs.concat(args),
+  p = spawn process.execPath, nodeArgs.concat(args),
     cwd:        process.cwd()
     env:        process.env
     customFds:  [0, 1, 2]
+  p.on 'exit', (code) -> process.exit code

 # Print the `--help` usage message and exit. Deprecated switches are not
 # shown.


Call underscore template properly

Call underscore template properly

--- coffee/toolbar.coffee
+++ coffee/toolbar.coffee
@@ -92,7 +92,7 @@
 class LC.Toolbar

   constructor: (@lc, @$el) ->
-    @$el.append(_.template(LC.toolbarTemplate))
+    @$el.append(_.template(LC.toolbarTemplate)())
     @initColors()
     @initButtons()
     @initTools()


handler登録が完了してからメッセージを投げるようにする

handler登録が完了してからメッセージを投げるようにする

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -39,7 +39,6 @@
       console.warn 'markdown parse error'

   componentDidMount: ->
-    @syncMarkdownFromServer()
     socketService.addMessageHandler (data)=>
       editor = @refs.editor.getDOMNode()
       caretStart = editor.selectionStart
@@ -53,6 +52,7 @@
           () =>
             editor.setSelectionRange(caretStart, caretEnd)
           @contentUpdateFromMarkdown
+    @syncMarkdownFromServer()

   sendMarkdown: (markdown)->
     socketService.sendRequest(


still simplifying testling test to see if we can get an empty test set run green in IE6

--- spec/CompatibilityTest.coffee
+++ spec/CompatibilityTest.coffee
@@ -1,6 +1,4 @@
 # A pruned version of BaconSpec, just to see if it runs nicer in Testling.CI
-expect = require("chai").expect
-
 describe "Just testing", ->
   it "should always work", ->
-    expect(true).to.equal(true)
+    console.log "hooray"


No global lookup for child view classes

No global lookup for child view classes

Improve compatibility with Ember 1.8+

--- packages/emberella/lib/views/autocomplete.coffee
+++ packages/emberella/lib/views/autocomplete.coffee
@@ -362,7 +362,7 @@
     @type Ember.View
     @default 'Emberella.AutocompleteInputView'
   ###
-  inputViewClass: 'Emberella.AutocompleteInputView'
+  inputViewClass: AutocompleteInputView

   ###
     The view class to use as the suggestion collection view.
@@ -371,7 +371,7 @@
     @type Ember.View
     @default 'Emberella.AutocompleteListView'
   ###
-  listViewClass: 'Emberella.AutocompleteListView'
+  listViewClass: AutocompleteListView

   ###
     The view class to use for individual suggestion listing views.
@@ -380,7 +380,7 @@
     @type Ember.View
     @default 'Emberella.AutocompleteItemView'
   ###
-  itemViewClass: 'Emberella.AutocompleteItemView'
+  itemViewClass: AutocompleteItemView

   ###
     A custom string.replace function to highlight matching strings in
@@ -1021,7 +1021,7 @@
   @uses Emberella.KeyboardControlMixin
   @uses Emberella.MembershipMixin
 ###
-Emberella.AutocompleteInputView = Ember.TextField.extend Emberella.FocusableMixin, Emberella.KeyboardControlMixin, Emberella.MembershipMixin,
+AutocompleteInputView = Emberella.AutocompleteInputView = Ember.TextField.extend Emberella.FocusableMixin, Emberella.KeyboardControlMixin, Emberella.MembershipMixin,
   attributeBindings: ['autocomplete']
   autocomplete: 'off' #disable browser autocomplete
   inherit: ['value', 'placeholder']
@@ -1041,7 +1041,7 @@
   @extends Ember.CollectionView
   @uses Emberella.MembershipMixin
 ###
-Emberella.AutocompleteListView = Ember.CollectionView.extend Emberella.MembershipMixin,
+AutocompleteListView = Emberella.AutocompleteListView = Ember.CollectionView.extend Emberella.MembershipMixin,
   inherit: ['itemViewClass', 'content:suggestions', 'isVisible:isListVisible']
   classNames: ['emberella-autocomplete-list']

@@ -1061,7 +1061,7 @@
   @extends Ember.View
   @uses Emberella.MembershipMixin
 ###
-Emberella.AutocompleteItemView = Ember.View.extend Emberella.MembershipMixin,
+AutocompleteItemView = Emberella.AutocompleteItemView = Ember.View.extend Emberella.MembershipMixin,
   inherit: ['template', 'highlighter', 'searchExpression', 'contentPath']

   classNames: ['emberella-autocomplete-item']


Adding missing parens

Adding missing parens

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -433,7 +433,7 @@
     editor.attachToDom()
     editor.insertText('thisIsAReallyReallyReallyLongCompletion ')
     editor.moveCursorToBottom()
-    editor.insertNewline
+    editor.insertNewline()
     editor.insertText('t')
     autocomplete.attach()
     expect(autocomplete.list.prop('scrollWidth')).toBe autocomplete.list.width()


:lipstick: Whitespaces

:lipstick: Whitespaces


Fix various specs for Windows

Fix various specs for Windows

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -8,6 +8,9 @@

 PathLoader = require '../lib/path-loader'
 DefaultFileIcons = require '../lib/default-file-icons'
+
+escapeSelector = (_selector) ->
+  _selector.replace(/\\/g, '\\\\')

 rmrf = (_path) ->
   if fs.statSync(_path).isDirectory()
@@ -113,7 +116,7 @@

           runs ->
             eachFilePath [rootDir1, rootDir2], (filePath) ->
-              item = projectView.list.find("li:contains(#{filePath})").eq(0)
+              item = projectView.list.find("li:contains(#{escapeSelector(filePath)})").eq(0)
               expect(item).toExist()
               nameDiv = item.find("div:first-child")
               expect(nameDiv).toHaveAttr("data-name", path.basename(filePath))
@@ -128,12 +131,12 @@

           runs ->
             eachFilePath [rootDir1], (filePath) ->
-              item = projectView.list.find("li:contains(#{filePath})").eq(0)
+              item = projectView.list.find("li:contains(#{escapeSelector(filePath)})").eq(0)
               expect(item).toExist()
               expect(item.find("div").eq(1)).toHaveText(path.join(path.basename(rootDir1), filePath))

             eachFilePath [rootDir2], (filePath) ->
-              item = projectView.list.find("li:contains(#{filePath})").eq(0)
+              item = projectView.list.find("li:contains(#{escapeSelector(filePath)})").eq(0)
               expect(item).toExist()
               expect(item.find("div").eq(1)).toHaveText(path.join(path.basename(rootDir2), filePath))

@@ -284,7 +287,7 @@

           runs ->
             eachFilePath [rootDir1], (filePath) ->
-              item = projectView.list.find("li:contains(#{filePath})").eq(0)
+              item = projectView.list.find("li:contains(#{escapeSelector(filePath)})").eq(0)
               expect(item).toExist()
               expect(item).not.toHaveText(path.basename(rootDir1))

@@ -964,28 +967,28 @@
     fileIcons = new DefaultFileIcons

     it "defaults to text", ->
-
+
       waitsForPromise ->
         atom.workspace.open('sample.js')
-
+
       runs ->
         dispatchCommand('toggle-buffer-finder')
         expect(atom.workspace.panelForItem(bufferView).isVisible()).toBe true
-
+
         bufferView.filterEditorView.getModel().insertText('js')
         bufferView.populateList()
         firstResult = bufferView.list.children('li').find('.primary-line')
         expect(fileIcons.iconClassForPath(firstResult[0].dataset.path)).toBe 'icon-file-text'
-
+
     it "shows image icons", ->
-
+
       waitsForPromise ->
         atom.workspace.open('sample.gif')
-
+
       runs ->
         dispatchCommand('toggle-buffer-finder')
         expect(atom.workspace.panelForItem(bufferView).isVisible()).toBe true
-
+
         bufferView.filterEditorView.getModel().insertText('gif')
         bufferView.populateList()
         firstResult = bufferView.list.children('li').find('.primary-line')


Add test for non-word characters

Add test for non-word characters

--- spec/spellchecker-spec.coffee
+++ spec/spellchecker-spec.coffee
@@ -20,12 +20,19 @@
         {start: 13, end: 18},
       ]

-    it "accounts for UTF16 pairs correctly", ->
+    it "accounts for UTF16 pairs", ->
       string = "😎 cat caat dog dooog"

       expect(SpellChecker.checkSpelling(string)).toEqual [
         {start: 7, end: 11},
         {start: 16, end: 21},
+      ]
+
+    it "accounts for other non-word characters", ->
+      string = "'cat' (caat. <dog> :dooog)"
+      expect(SpellChecker.checkSpelling(string)).toEqual [
+        {start: 7, end: 11},
+        {start: 20, end: 25},
       ]

   describe ".getCorrectionsForMisspelling(word)", ->


Correct typo in spec description

Correct typo in spec description

--- spec/themes-panel-spec.coffee
+++ spec/themes-panel-spec.coffee
@@ -54,7 +54,7 @@
       panel.syntaxMenu.val('atom-light-syntax').trigger('change')
       expect(atom.config.get('core.themes')).toEqual ['atom-dark-ui', 'atom-light-syntax']

-  describe "when the 'core.config' key is changes", ->
+  describe "when the 'core.config' key changes", ->
     it "refreshes the theme menus", ->
       reloadedHandler.reset()
       atom.config.set('core.themes', ['atom-light-ui', 'atom-light-syntax'])


button timeout state

button timeout state

--- src/components/async-button.cjsx
+++ src/components/async-button.cjsx
@@ -1,9 +1,26 @@
 React = require 'react'
 BS = require 'react-bootstrap'
+_ = require 'underscore'
 RefreshButton = require './refresh-button'

 module.exports = React.createClass
   displayName: 'AsyncButton'
+
+  getInitialState: ->
+    isTimedout: false
+
+  componentDidUpdate: ->
+    {isWaiting, timeout} = @props
+    {isTimedout} = @state
+
+    if isWaiting and not isTimedout
+      _.delay =>
+        @checkForTimeout()
+      , timeout
+
+  checkForTimeout: ->
+    {isWaiting} = @props
+    @setState(isTimedout: true) if isWaiting

   propTypes:
     isWaiting: React.PropTypes.bool.isRequired
@@ -13,6 +30,7 @@
     failedState: React.PropTypes.func
     failedProps: React.PropTypes.object
     doneText: React.PropTypes.any
+    timeout: React.PropTypes.number

   getDefaultProps: ->
     isDone: false
@@ -22,13 +40,17 @@
     failedProps:
       beforeText: 'There was a problem.  '
     doneText: ''
+    timeout: 30000

   render: ->
     {className, disabled} = @props
     {isWaiting, isDone, isFailed} = @props
     {children, waitingText, failedState, failedProps, doneText} = @props
+    {isTimedout} = @state

-    if isFailed
+    buttonTypeClass = 'async-button'
+
+    if isFailed or isTimedout
       stateClass = 'is-failed'
       return <failedState {...failedProps}/>
     else if isWaiting
@@ -44,7 +66,7 @@
       text = children

     <BS.Button {...@props}
-      className={[stateClass, className]}
+      className={[buttonTypeClass, stateClass, className]}
       disabled={disabled}
       >
         {spinner}


Added test for issue 96

Added test for issue 96

--- test/ecosystem/backbone-relational/test.coffee
+++ test/ecosystem/backbone-relational/test.coffee
@@ -816,3 +816,89 @@

     assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
     done()
+
+  it '12. Issue 96', (done) ->
+    kb.statistics = new kb.Statistics() # turn on stats
+
+    Parameter = Backbone.RelationalModel.extend({idAttribute: "ParameterName"})
+    ParameterCollection = Backbone.Collection.extend({model: Parameter})
+
+    Filter = Backbone.RelationalModel.extend({
+      relations:[{
+        type: Backbone.HasOne,
+        key: "Parameter",
+        keySource: "ParameterName",
+        relatedModel: Parameter,
+        includeInJSON: "ParameterName",
+        reverseRelation: {
+          key: "Filters",
+          type: Backbone.HasMany,
+          includeInJSON: false
+        }
+      }]
+    })
+
+    class StringParameterViewModel extends kb.ViewModel
+    class BooleanParameterViewModel extends kb.ViewModel
+
+    ParameterFactory =
+      create: (parameter, options) ->
+        switch (parameter.get("Type"))
+          when 0 then return new StringParameterViewModel(parameter, options)
+          when 1 then return new BooleanParameterViewModel(parameter, options)
+          else throw "Invalid parameter type attribute."
+
+    parameter_models = new ParameterCollection()
+    parameters = kb.collectionObservable(parameter_models, {factories: {"models": ParameterFactory}})
+
+    parameter_models.push({Type: 0})
+    parameter_models.push({Type: 1})
+
+    assert.ok(parameters()[0] instanceof StringParameterViewModel)
+    assert.ok(parameters()[1] instanceof BooleanParameterViewModel)
+
+    kb.release(parameters)
+
+    FilterGroup = Backbone.RelationalModel.extend({
+      relations:[{
+        type: Backbone.HasMany,
+        key: "Filters",
+        relatedModel: Filter
+      }]
+    })
+
+    class FilterViewModel extends kb.ViewModel
+
+    class FilterGroupViewModel extends kb.ViewModel
+      constructor: (model, options) ->
+        super model, {
+          factories:
+            "Filters.models": FilterViewModel
+            "Filters.models.Parameter": ParameterFactory
+          options: options
+        }
+
+    filter_group_model = new FilterGroup({Filters: [
+      {Name: 'String', Parameter: {Type: 0}}
+      {Name: 'Boolean', Parameter: {Type: 1}}
+    ]})
+
+    # LEGACY
+    unless filter_group_model.get('Filters').models[0].get('Parameter')
+      assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
+      return done()
+
+    filter_group = new FilterGroupViewModel(filter_group_model)
+
+    filter = filter_group.Filters()[0]
+    assert.equal(filter.Name(), 'String')
+    assert.ok(filter.Parameter() instanceof StringParameterViewModel)
+
+    filter = filter_group.Filters()[1]
+    assert.equal(filter.Name(), 'Boolean')
+    assert.ok(filter.Parameter() instanceof BooleanParameterViewModel)
+
+    kb.release(filter_group)
+
+    assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
+    done()


No longer needed

No longer needed

--- SingularityUI/app/application.coffee
+++ SingularityUI/app/application.coffee
@@ -1,7 +1,6 @@
 Router = require 'lib/router'

 User = require 'models/User'
-State = require 'models/State'

 TasksActive = require 'collections/TasksActive'
 TasksScheduled = require 'collections/TasksScheduled'
@@ -88,8 +87,6 @@
         @show view

     setupAppCollections: ->
-        @state = new State
-
         resources = [{
             collection_key: 'tasksActive'
             collection: TasksActive


Fix tasks reversing sort order fuzzy search

--- SingularityUI/app/views/tasks.coffee
+++ SingularityUI/app/views/tasks.coffee
@@ -81,7 +81,7 @@
                 return value
             if not @sortAscending
                 tasks = tasks.reverse()
-        else
+        else if not @searchFilter
             tasks.reverse() unless @state is 'scheduled'

         @currentTasks = tasks


Delay MailChimp subscribing after account creation by 30min

--- server/models/User.coffee
+++ server/models/User.coffee
@@ -162,6 +162,9 @@
   return callback?() unless isProduction or GLOBAL.testing
   return callback?() if doc.updatedMailChimp
   return callback?() unless doc.get('email')
+  return callback?() unless doc.get('dateCreated')
+  accountAgeMinutes = (new Date().getTime() - doc.get('dateCreated').getTime?() ? 0) / 1000 / 60
+  return callback?() unless accountAgeMinutes > 30 or GLOBAL.testing
   existingProps = doc.get('mailChimp')
   emailChanged = (not existingProps) or existingProps?.email isnt doc.get('email')


JAccount:checkPermission: remove not used instance method

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -485,16 +485,6 @@
               console.warn '[JAccount.changeUsername]', err  if err?

-  checkPermission: (target, permission, callback) ->
-    JPermissionSet = require './group/permissionset'
-    client =
-      context     : { group: target.slug }
-      connection  : { delegate: this }
-    advanced =
-      if Array.isArray permission then permission
-      else JPermissionSet.wrapPermission permission
-    JPermissionSet.checkPermission client, advanced, target, null, callback
-
   @renderHomepage: require '../render/profile.coffee'


Adds additional information about where an expression was not equal

--- javascripts/lib/math/expression_equality.coffee
+++ javascripts/lib/math/expression_equality.coffee
@@ -11,15 +11,15 @@

     logger = logger_builder.build(stringify_objects: false)

-    buildIsEqual = (for_type)->
-      isEqualFunction = (other)->
-        if other instanceof for_type
-          if @_simpleIsEqual
-            @_simpleIsEqual(other)
+    buildIsEqual = (for_type, additional_method=false)->
+      isEqualFunction = (other, eq_calc)->
+        same_type = eq_calc.saveFalseForReport((other instanceof for_type), @unrefined(), other,
+          "Wrong types")
+        if same_type
+          if additional_method
+            @[additional_method](other, eq_calc)
           else
             true
-        else if other instanceof comps.expression
-          ref.refine(other).isEqual @
         else
           false
       logger.instrument(name: "buildIsEqual function", fn: isEqualFunction)
@@ -46,34 +46,52 @@

     # Exponentiation
     ref.forType(comps.exponentiation, {
-      isEqual: (buildIsEqual(comps.exponentiation)),
-      _simpleIsEqual: (other)->
-        ref.refine(@base()).isEqual(other.base()) and
-          ref.refine(@power()).isEqual(other.power())
+      isEqual: (buildIsEqual(comps.exponentiation, "checkBaseAndPowerEquality")),
+      checkBaseAndPowerEquality: (other, eq_comp)->
+        base_equal = ref.refine(@base()).isEqual(other.base(), eq_comp)
+        power_equal = ref.refine(@power()).isEqual(other.power(), eq_comp)
+        base_equal && power_equal # no need to save, report comes from below
       })

     # Expression
     ref.forType(comps.expression, {
-      isExpressionEqual: (other)->
+      isExpressionEqual: (other, eq_calc)->
         logger.info("isExpressionEqual", @unrefined(), other)
-        if @is_error == other.is_error && @is_open == other.is_open
-          if _(@expression).size() == _(other.expression).size()
-            contains_unequal =
-              _.chain(@expression).map((element, i)->
-                ref.refine(element).isEqual(other.nth(i))
-              ).contains(false).value()
-            !contains_unequal
-          else
-            false
+        match_error = @is_error == other.is_error
+        eq_calc.saveFalseForReport(match_error, @unrefined(), other, "error values not equal")
+
+        match_open = @is_open == other.is_open
+        eq_calc.saveFalseForReport(match_open, @unrefined(), other, "open values not equal")
+
+        match_size = _(@expression).size() == _(other.expression).size()
+        eq_calc.saveFalseForReport(match_size, @unrefined(), other, "size values not equal")
+
+        if match_error && match_open && match_size
+          contains_unequal =
+            _.chain(@expression).map((element, i)->
+              ret = ref.refine(element).isEqual(other.nth(i), eq_calc)
+              debugger unless ret
+              ret
+            ).contains(false).value()
+          !contains_unequal
         else
           false

-      isEqual: (other)->
-        if other instanceof comps.expression
-          @isExpressionEqual(other)
+      isEqual: (other, eq_calc)->
+
+        same_type = eq_calc.saveFalseForReport((other instanceof comps.expression), @unrefined(), other,
+          "Wrong types")
+        if same_type
+          @isExpressionEqual(other, eq_calc)
+        else
+          false
     })

+    # Equals
+    ref.forType(comps.equals, {
+      isEqual: (buildIsEqual(comps.equals))
+    })

     # Multiplication
@@ -81,30 +99,20 @@
       isEqual: (buildIsEqual(comps.multiplication))
       })

-
     # Number
     ref.forType(comps.number, {
-      isEqual: (buildIsEqual(comps.number)),
-      _simpleIsEqual: (other)->
+      isEqual: (buildIsEqual(comps.number, "checkNumberValues")),
+      checkNumberValues: (other, eq_calc)->
+
         # this is bad; TODO fix
         # The real "todo" here is that
         # numbers are internally of questionable state.
         # Im not entirely sure how to state that number expresison components are equal
-        "#{@value()}" == "#{other.value()}"
+        check = "#{@value()}" == "#{other.value()}"
+        eq_calc.saveFalseForReport(check, @unrefined(), other, "Numeric values not equal")
     })

-    ref.forType(comps.addition, {
-      isEqual: (buildIsEqual(comps.addition))
-      })

-    ref.forType(comps.multiplication, {
-      isEqual: (buildIsEqual(comps.multiplication))
-      })
-
-
-    ref.forType(comps.blank, {
-      isEqual: (buildIsEqual(comps.blank))
-      })

     ref.forType(comps.exponentiation, {
       isEqual: (buildIsEqual(comps.exponentiation)),
@@ -117,7 +125,8 @@
     ref.forDefault({
       isEqual: -> console.log(@); throw "NOT IMPLEMENTED"
     })
-    # Number
+
+    # Pi
     ref.forType(comps.pi, {
       isEqual: (buildIsEqual(comps.pi))
     })
@@ -128,14 +137,35 @@
     })

     class ExpressionEquality
-      initialize: (@first)->
-      isEqual: (@second)->
+      initialize: ->
+        @report_saved = false
+
+      calculate: (@first, @second)->
         firstp = ref.refine @first
-        firstp.isEqual @second
+        @_equality_results = firstp.isEqual @second, @
+        @
+
+      isEqual: ->
+        @_equality_results
+
+      notEqualReport: (@a, @b, @not_eql_msg)->
+        @report_saved = true
+
+      saveFalseForReport: (value, a, b, msg)->
+        if value
+          true
+        else
+          @notEqualReport(a, b, msg) unless @report_saved
+          false

     class_mixer ExpressionEquality

     ExpressionEquality.isEqual = (a,b)->
-      ExpressionEquality.build(a).isEqual(b)
+      ExpressionEquality.build().calculate(a,b).isEqual()
+
+    ExpressionEquality.equalityCalculation = (a,b)->
+      ec = ExpressionEquality.build()
+      ec.calculate(a,b)
+      ec

     return ExpressionEquality


Separated rendering of graph from the GETing of the data

--- picoCTF-web/web/coffee/progression_graphs.coffee
+++ picoCTF-web/web/coffee/progression_graphs.coffee
@@ -146,36 +146,37 @@
     apiCall "GET", "/api/stats/top_teams/score_progression", {}
     .done drawgraph

+@renderTeamProgressionGraph = (selector, data) ->
+  div = divFromSelector selector
+  apiCall "GET", "/api/time", {}
+  .done (timedata) ->
+    if data.status == 1
+      if data.data.length > 0 and data.data[0].length > 0
+
+        dataPoints = progressionDataToPoints data.data, numDataPoints, timedata.data
+
+        datasets = [
+            label: data.data.name
+            data: dataPoints
+            pointColor: strokeColors[0]
+            strokeColor: strokeColors[0]
+            fillColor: fillColors[0]
+        ]
+
+        data =
+          labels: ("" for i in [1...numDataPoints])
+          datasets: datasets
+
+        parent = $(div).parent()
+        $(div).attr('width', parent.width())
+        $(div).attr('height', parent.height())
+
+        chart = new Chart(div.getContext("2d")).Line data, teamChartSettings
+
+      else
+          $(selector).html("
You have not solved any enabled problems.
")
+
 @drawTeamProgressionGraph = (selector, container_selector) ->
-  div = divFromSelector selector
   apiCall "GET", "/api/stats/team/score_progression", {}
   .done (data) ->
-    apiCall "GET", "/api/time", {}
-    .done (timedata) ->
-      if data.status == 1
-          if data.data.length > 0 and data.data[0].length > 0
-
-            dataPoints = progressionDataToPoints data.data, numDataPoints, timedata.data
-
-            datasets = [
-                label: data.data.name
-                data: dataPoints
-                pointColor: strokeColors[0]
-                strokeColor: strokeColors[0]
-                fillColor: fillColors[0]
-            ]
-
-            data =
-              labels: ("" for i in [1...numDataPoints])
-              datasets: datasets
-
-            parent = $(div).parent()
-            $(div).attr('width', parent.width())
-            $(div).attr('height', parent.height())
-
-            chart = new Chart(div.getContext("2d")).Line data, teamChartSettings
-
-          else
-            $(selector).html("
You have not solved any enabled problems.
")
-        else
-          $(container_selector).hide()
+    renderTeamProgressionGraph selector, data


Add mouseover and mouseout events to available event bindings.

--- lib/assets/javascripts/twine.js.coffee
+++ lib/assets/javascripts/twine.js.coffee
@@ -310,5 +310,5 @@
     return teardown: ->
       $(node).off eventName, onEventHandler

-for eventName in ['click', 'dblclick', 'mousedown', 'mouseup', 'submit', 'dragenter', 'dragleave', 'dragover', 'drop', 'drag', 'change', 'keypress', 'keydown', 'keyup', 'input', 'error', 'done', 'success', 'fail', 'blur', 'focus', 'load']
+for eventName in ['click', 'dblclick', 'mouseover', 'mouseout', 'mousedown', 'mouseup', 'submit', 'dragenter', 'dragleave', 'dragover', 'drop', 'drag', 'change', 'keypress', 'keydown', 'keyup', 'input', 'error', 'done', 'success', 'fail', 'blur', 'focus', 'load']
   setupEventBinding(eventName)


added check to see if suggested tag is duplicate.

--- server/Translator.coffee
+++ server/Translator.coffee
@@ -43,7 +43,8 @@

     for post in Posts.find( 'parent_id': server.parent_id ).fetch()
       for key of post.tags
-        @suggested_tags.push( key ) unless key of @my_tags
+        unless key of @my_tags or key of @tags
+          @suggested_tags.push( key )

     author = Meteor.users.findOne( '_id': server.author_id )
     if author?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,4 +1,4 @@
-ClampBounds = (I={}) ->
+ClampBounds = (I={}, self) ->
   self.bind "update", ->
     ; # Add update method behavior


Remove App.initialize from coffeescript template

--- lib/generators/templates/app.js.coffee
+++ lib/generators/templates/app.js.coffee
@@ -7,5 +7,3 @@
 #= require_tree ./routes
 #= require ./router
 #= require_self
-
-<%= application_name.camelize %>.initialize()


fix test queries

fix test queries

--- test.coffee
+++ test.coffee
@@ -274,14 +274,14 @@
       opts = {poll}

       it 'returns a result it already applies to', (done) -> @create {x:5}, =>
-        @collection.query {'data.x':5}, opts, (err, emitter) =>
+        @collection.query {'x':5}, opts, (err, emitter) =>
           expected = [docName:@docName, data:{x:5}, type:otTypes.json0.uri, v:1, c:@cName]
           assert.deepEqual emitter.data, expected
           emitter.destroy()
           done()

       it 'gets an empty result set when you query something with no results', (done) ->
-        @collection.query {'data.xyz':123}, opts, (err, emitter) ->
+        @collection.query {'xyz':123}, opts, (err, emitter) ->
           assert.deepEqual emitter.data, []
           emitter.on 'add', -> throw new Error 'should not have added results'

@@ -290,7 +290,7 @@
             done()

       it 'adds an element when it matches', (done) ->
-        @collection.query {'data.x':5}, opts, (err, emitter) =>
+        @collection.query {'x':5}, opts, (err, emitter) =>
           emitter.on 'add', (data, idx) =>
             assert.deepEqual data, {docName:@docName, v:1, data:{x:5}, type:otTypes.json0.uri}
             assert.strictEqual idx, 0
@@ -301,7 +301,7 @@
           @create {x:5}

       it 'remove an element that no longer matches', (done) -> @create {x:5}, =>
-        @collection.query {'data.x':5}, opts, (err, emitter) =>
+        @collection.query {'x':5}, opts, (err, emitter) =>
           emitter.on 'remove', (doc, idx) =>
             assert.strictEqual idx, 0
             assert.strictEqual doc.docName, @docName
@@ -318,7 +318,7 @@
           @collection.submit @docName, v:1, op:[{p:['x'], od:5, oi:6}], (err, v) =>

       it 'removes deleted elements', (done) -> @create {x:5}, =>
-        @collection.query {'data.x':5}, opts, (err, emitter) =>
+        @collection.query {'x':5}, opts, (err, emitter) =>
           assert.strictEqual emitter.data.length, 1

           emitter.on 'remove', (doc, idx) =>
@@ -334,7 +334,7 @@

       it 'does not emit receive events to a destroyed query', (done) ->
-        @collection.query {'data.x':5}, opts, (err, emitter) =>
+        @collection.query {'x':5}, opts, (err, emitter) =>
           emitter.on 'add', -> throw new Error 'add called after destroy'
           emitter.on 'remove', -> throw new Error 'remove called after destroy'

@@ -352,19 +352,19 @@
         @create2 '_p1', {x:5, i:1}, => @create2 '_p2', {x:5, i:2}, => @create2 '_p3', {x:5, i:3}, => callback()

       it 'respects limit queries', (done) ->
-        @collection.query {$query:{'data.x':5}, $orderby:{'data.i':1}, $limit:1}, {poll:true}, (err, emitter) ->
+        @collection.query {$query:{'x':5}, $orderby:{'i':1}, $limit:1}, {poll:true}, (err, emitter) ->
           assert.strictEqual emitter.data.length, 1
           assert.strictEqual emitter.data[0].docName, '_p1'
           done()

       it 'respects skips', (done) ->
-        @collection.query {$query:{'data.x':5}, $orderby:{'data.i':1}, $limit:1, $skip:1}, {poll:true}, (err, emitter) ->
+        @collection.query {$query:{'x':5}, $orderby:{'i':1}, $limit:1, $skip:1}, {poll:true}, (err, emitter) ->
           assert.strictEqual emitter.data.length, 1
           assert.strictEqual emitter.data[0].docName, '_p2'
           done()

       it 'will insert an element in the set', (done) ->
-        @collection.query {$query:{'data.x':5}, $orderby:{'data.i':1}}, {poll:true}, (err, emitter) =>
+        @collection.query {$query:{'x':5}, $orderby:{'i':1}}, {poll:true}, (err, emitter) =>
           assert.equal emitter.data.length, 3

           emitter.on 'add', (data, idx) =>
@@ -378,7 +378,7 @@
           @create2 '_p4', {x:5, i:1.5}

       it 'will remove an element from the set', (done) ->
-        @collection.query {$query:{'data.x':5}, $orderby:{'data.i':1}}, {poll:true}, (err, emitter) =>
+        @collection.query {$query:{'x':5}, $orderby:{'i':1}}, {poll:true}, (err, emitter) =>

           emitter.once 'remove', (data, idx) ->
             assert.strictEqual idx, 0
@@ -400,7 +400,7 @@

     describe 'queryFetch', ->
       it 'query fetch with no results works', (done) ->
-        @collection.queryFetch {'data.somekeythatdoesnotexist':1}, (err, results) ->
+        @collection.queryFetch {'somekeythatdoesnotexist':1}, (err, results) ->
           throw new Error err if err
           assert.deepEqual results, []
           done()


Closes #973. New is more careful around the do construct.

--- nodes.coffee
+++ nodes.coffee
@@ -1212,10 +1212,13 @@
 exports.Op = class Op extends Base
   constructor: (op, first, second, flip) ->
     return new In first, second if op is 'in'
-    return new Call first, first.params or [] if op is 'do'
+    if op is 'do'
+      call = new Call first, first.params or []
+      call.do = yes
+      return call
     if op is 'new'
-      return first.newInstance() if first instanceof Call
-      first = new Parens first   if first instanceof Code and first.bound
+      return first.newInstance() if first instanceof Call and not first.do
+      first = new Parens first   if first instanceof Code and first.bound or first.do
     @operator = CONVERSIONS[op] or op
     @first    = first
     @second   = second


Fixing wonky spacing at top level.

Fixing wonky spacing at top level.

--- nodes.coffee
+++ nodes.coffee
@@ -229,7 +229,6 @@
         code = node.compile o
         unless node.isStatement o
           code = "#{@tab}#{code};"
-          code = "#{code}\n" if node instanceof Literal
         codes.push code
       else
         codes.push node.compile o, LEVEL_LIST


fixed misdentation in `a?.b = c`

fixed misdentation in `a?.b = c`

--- nodes.coffee
+++ nodes.coffee
@@ -863,7 +863,9 @@
     if isValue = @isValue()
       return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject()
       return @compileSplice(o) if @variable.isSplice()
-      return node.compile o if node = Value.unfoldSoak o, this, 'variable'
+      if ifn = Value.unfoldSoak o, this, 'variable'
+        delete o.top
+        return ifn.compile o
     top    = del o, 'top'
     stmt   = del o, 'asStatement'
     name   = @variable.compile(o)
@@ -1526,23 +1528,22 @@
   elseBodyNode: -> @elseBody?.unwrap()

   # Rewrite a chain of **Ifs** to add a default case as the final *else*.
-  addElse: (elseBody, statement) ->
+  addElse: (elseBody) ->
     if @isChain
-      @elseBodyNode().addElse elseBody, statement
+      @elseBodyNode().addElse elseBody
     else
-      @isChain = elseBody instanceof If
+      @isChain  = elseBody instanceof If
       @elseBody = @ensureExpressions elseBody
     this

   # The **If** only compiles into a statement if either of its bodies needs
   # to be a statement. Otherwise a conditional operator is safe.
   isStatement: (o) ->
-    @statement or= !!((o and o.top) or @bodyNode().isStatement(o) or (@elseBody and @elseBodyNode().isStatement(o)))
+    @statement or= o?.top or @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)

   compileCondition: (o) ->
-    conditions = flatten [@condition]
-    conditions[0].parenthetical = yes if conditions.length is 1
-    (cond.compile(o) for cond in conditions).join(' || ')
+    @condition.parenthetical = yes
+    @condition.compile o

   compileNode: (o) ->
     if @isStatement o then @compileStatement o else @compileExpression o
@@ -1566,16 +1567,13 @@
     condO    = merge o
     o.indent = @idt 1
     o.top    = true
-    ifDent   = if child or (top and not @isStatement(o)) then '' else @idt()
-    comDent  = if child then @idt() else ''
-    body     = @body.compile(o)
-    ifPart   = "#{ifDent}if (#{ @compileCondition(condO) }) {\n#{body}\n#{@tab}}"
+    ifPart   = "if (#{ @compileCondition condO }) {\n#{ @body.compile o }\n#{@tab}}"
+    ifPart   = @tab + ifPart unless child
     return ifPart unless @elseBody
-    elsePart = if @isChain
-      ' else ' + @elseBodyNode().compile(merge(o, {indent: @idt(), chainChild: true}))
+    ifPart + if @isChain
+      ' else ' + @elseBodyNode().compile merge o, indent: @tab, chainChild: true
     else
       " else {\n#{ @elseBody.compile(o) }\n#{@tab}}"
-    "#{ifPart}#{elsePart}"

   # Compile the If as a conditional operator.
   compileExpression: (o) ->


Fix property name.

Fix property name.

--- src/spec/sync/utils/category-utils.spec.coffee
+++ src/spec/sync/utils/category-utils.spec.coffee
@@ -136,7 +136,7 @@
     it 'should create action to change external id', ->
       category =
         id: '123'
-        external: 'something'
+        externalId: 'something'

       otherCategory = _.deepClone category
       otherCategory.externalId = 'ext-123'


check isMember and isModerator by user.memberGrps and moderatorGrps

--- api/services/user.coffee
+++ api/services/user.coffee
@@ -18,14 +18,14 @@
 		owner == user?.id

 	isModerator: (user, group) ->
-		_.any group?.moderators, (item) ->
-			owner = item.id || item
-			owner == user?.id
+		_.any user?.moderatorGrps, (item) ->
+			grp = item.id || item
+			grp == group?.id

 	isMember: (user, group) ->
-		group?.type == 'Unmoderated' or _.any group?.members, (item) ->
-			owner = item.id || item
-			owner == user?.id
+		group?.type == 'Unmoderated' or _.any user?.memberGrps, (item) ->
+			grp = item.id || item
+			grp == group?.id

 	isVisitor: (user, group) ->
 		group?.type == 'Moderated'


introduction-tooltip: Prevented showing previously showed tooltip.

--- client/app/CommonViews/introductiontooltip/introductiontooltipcontroller.coffee
+++ client/app/CommonViews/introductiontooltip/introductiontooltipcontroller.coffee
@@ -4,10 +4,11 @@

     super options, data

-    @currentTimestamp = Date.now()
-    @shouldAddOverlay = no
-    @visibleTooltips  = []
-    @stepByStepGroups = {}
+    @currentTimestamp  = Date.now()
+    @shouldAddOverlay  = no
+    @visibleTooltips   = []
+    @displayedTooltips = []
+    @stepByStepGroups  = {}

     @getSingleton("mainController").on "FrameworkIsReady", =>
       @init()
@@ -47,6 +48,9 @@

     return if not assets or @isExpired data.expiryDate
     return if data.visibility is "stepByStep" and data.index > @stepByStepGroups[data.groupName].currentIndex
+    return if @displayedTooltips.indexOf(data.introId) > -1
+
+    @displayedTooltips.push data.introId

     {parentView, tooltipView} = assets
     tooltip = new IntroductionTooltip {


Remove unused code from previous payment aproach

--- client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -259,40 +259,3 @@
       else
         slugView.updatePartial "#{location.protocol}//#{location.host}/#{newSlug}"
         slugInput.setValue newSlug
-
-  submit_FOR_PAYMENT_DEPRECATED: ->
-
-    # Copy account creator's billing information
-    KD.remote.api.JRecurlyPlan.getUserAccount (err, data)=>
-      warn err
-      if err or not data
-        data = {}
-
-      # These will go into Recurly module
-      delete data.cardNumber
-      delete data.cardMonth
-      delete data.cardYear
-      delete data.cardCV
-
-      modal = createAccountPaymentMethodModal data, (newData, onError, onSuccess)=>
-        # These will go into Recurly module
-        newData.username    = 'group_unnamed'
-        newData.ipAddress   = '0.0.0.0'
-        newData.firstName   = 'Group'
-        newData.lastName    = 'Unnamed'
-        newData.email       = 'group@example.com'
-        newData.pin         = 'xxx'
-        newData.accountCode = yes
-
-        {selector}   = @hostSelector.inputs
-        index        = parseInt selector.getValue(), 10
-
-        selectedPlan = @plans[index]
-        selectedPlan.subscribe newData, (err, subscription)->
-          if err
-            # Show error messages here.
-            onError()
-          else
-            # DONE
-            console.log "Subscribed user account:", subscription.userCode
-            onSuccess()


Only show toolbar once.

Only show toolbar once.

Otherwise you kill any event handlers tied to elements within.

--- scripts/views/layouts/workspace/menu.coffee
+++ scripts/views/layouts/workspace/menu.coffee
@@ -32,7 +32,8 @@

       @add.show(new AddView {collection:mediaTypes})
       @auth.show(new AuthView {model: session})
-      @toolbar.show(_toolbar)
+      if _toolbar != @toolbar.currentView
+        @toolbar.show(_toolbar)

     showToolbar: (view) ->
       @showView(view or toolbarView)


findSvc angularjs service now can search a folder in a list of folders, instead of searching in the list of folders stored in the root scope.

If no list is passed to the service, the default behavior is still to search in the root scope, so this shouldn't break anything.

This will enable us to search in the list of folders returned by the server. This will be necessary to change the behavior when loading feeds: instead of just replacing the list of folders in the root scope with the folders returned by the server (which will replace all folder objects with new ones), only new folders will be inserted in the scope (and folders in the scope not present in the list returned by the server will be removed). This way folders that have not changed will not have the object that represents them replaced by a new identical one.

--- app/assets/javascripts/ng-services/ng-FindSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FindSvc.js.coffee
@@ -6,7 +6,9 @@
 ['$rootScope', '$filter', ($rootScope, $filter)->

   #---------------------------------------------
-  # Find a feed given its id
+  # Find a feed given its id.
+  # If a list of feeds is passed, search in the list. Otherwise, search in the list
+  # of feeds stored in the root scope.
   #---------------------------------------------
   find_feed: (id, list = $rootScope.feeds)->
     if list
@@ -20,16 +22,18 @@
       return null

   #---------------------------------------------
-  # Find a folder given its id
+  # Find a folder given its id.
+  # If a list of folders is passed, search in the list. Otherwise, search in the list
+  # of folders stored in the root scope
   #---------------------------------------------
-  find_folder: (id)->
-    if $rootScope.folders
+  find_folder: (id, list = $rootScope.folders)->
+    if list
       if id == 'none'
         return null
       else if id == "all"
         return {id: "all"}
       else
-        folders = $filter('filter') $rootScope.folders, (folder)->
+        folders = $filter('filter') list, (folder)->
           return folder.id == id
         if folders?.length > 0
           return folders[0]


Fix an initialization order goof.

Fix an initialization order goof.

--- app/assets/javascripts/views/search_results_grid.coffee
+++ app/assets/javascripts/views/search_results_grid.coffee
@@ -20,10 +20,11 @@
       inlineFilters: true
     )

+    @set 'view', view
+
     grid = new Slick.Grid el, @get('view'), @get('columns'), @get('options')

     @set 'grid', grid
-    @set 'view', view

     view.onRowCountChanged.subscribe (e, args) ->
       grid.updateRowCount()


make keyboard shortcut dialog wording consistent

fixes CNVS-32758

test plan:
- make sure the title attr and screenreader text both say "keyboard
shortcuts" for the icon above a tinymce editor that opens the shortcuts
modal

Change-Id: I3cc240a6d2c4545d48122728021de298c782305e
Reviewed-on: https://gerrit.instructure.com/93519
Tested-by: Jenkins
Reviewed-by: Cody Cutrer <c4fe2b1d90ef8f2548c8aeabfa633434316f0305@instructure.com>
QA-Review: Benjamin Christian Nelson <4a283f6b032925e40a485c88ede5597d7d65a090@instructure.com>
Product-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>

--- app/coffeescripts/views/editor/KeyboardShortcuts.coffee
+++ app/coffeescripts/views/editor/KeyboardShortcuts.coffee
@@ -40,7 +40,7 @@
       },
       {
         key: 'ALT+F8',
-        description: I18n.t('keybindings.open_dialog', 'Open this help dialog')
+        description: I18n.t('Open this keyboard shortcuts dialog')
       }
     ]

@@ -48,7 +48,7 @@

     initialize: ->
       this.el.href = '#' # for keyboard accessibility
-      $(this.el).attr("title", I18n.t("editor_help_link", "Open Help Menu"))
+      $(this.el).attr("title", I18n.t('dialog_title', 'Keyboard Shortcuts'))

       $('<i class="icon-keyboard-shortcuts" aria-hidden="true" />').appendTo(this.el)
       $('<span class="screenreader-only" />')


Added Readmill.Store for working with local storage

--- src/annotator.readmill.coffee
+++ src/annotator.readmill.coffee
@@ -1,11 +1,32 @@
 class Readmill extends Annotator.Plugin
-  @CLIENT_ID: "454f5cfd1e794930c6fa99a94faad810"
   @API_ENDPOINT: "http://localhost:8000"

-Readmill.utils =
+  constructor: (options) ->
+    super {}, options
+
+    @auth  = new Readmill.Auth @options
+    @store = new Readmill.Store
+
+    token = options.accessToken || @store.get "access-token"
+    @connected(token) if token
+
+  connect: ->
+    @auth.connect().then @_onConnectSuccess, @_onConnectError
+
+  connected: (accessToken) ->
+    @client = new Readmill.Client(accessToken: accessToken)
+    @store.set "access-token", accessToken
+
+  _onConnectSuccess: (params) =>
+    @connected(params.access_token)
+
+  _onConnectError: (error) =>
+
+utils =
   serializeQueryString: (obj, sep="&", eq="=") ->
     esc = window.encodeURIComponent
     ("#{esc(key)}#{eq}#{esc(value)}" for own key, value of obj).join(sep)
+
   parseQueryString: (str, sep="&", eq="=") ->
     obj = {}
     decode = window.decodeURIComponent
@@ -14,19 +35,64 @@
       obj[decode(key)] = decode value
     obj

-class Readmill.Auth
+class Client
+  fetchMe: ->
+
+class Store
+  @KEY_PREFIX: "annotator.readmill/"
+  @CACHE_DELIMITER: "--cache--"
+
+  @localStorage: window.localStorage
+
+  @now: -> (new Date()).getTime()
+
+  get: (key) ->
+    value = Store.localStorage.getItem @prefixed(key)
+    if value
+      value = @checkCache value
+      @remove(key) unless value
+    JSON.parse value
+
+  set: (key, value, time) ->
+    value = JSON.stringify value
+    value = (Store.now() + time) + Store.CACHE_DELIMITER + value if time
+
+    try
+      Store.localStorage.setItem @prefixed(key), value
+    catch error
+      this.trigger 'error', [error, key, value, this]
+    this
+
+  remove: (key) ->
+    Store.localStorage.removeItem @prefixed(key)
+    this
+
+  prefixed: (key) ->
+    Store.KEY_PREFIX + key
+
+  checkCache: (value) ->
+    if value.indexOf(Store.CACHE_DELIMITER) > -1
+      # If the expiry time has passed then return null.
+      cached = value.split Store.CACHE_DELIMITER
+      value = if Store.now() > cached.shift()
+      then null else cached.join(Store.CACHE_DELIMITER)
+    value
+
+class Auth
   @AUTH_ENDPOINT: "http://localhost:8000/oauth/authorize"
+
   constructor: (options) ->
     {@clientId, @callbackUri, @authEndpoint} = options
-    @authEndpoint = Readmill.Auth.AUTH_ENDPOINT unless @authEndpoint
+    @authEndpoint = Auth.AUTH_ENDPOINT unless @authEndpoint
+
   connect: ->
     params =
       response_type: "code"
       client_id: @clientId
       redirect_uri: @callbackUri
-    qs = Readmill.utils.serializeQueryString(params)
+    qs = utils.serializeQueryString(params)

-    Readmill.Auth.callback = @callback
+    Auth.callback = @callback

     @popup = @openWindow "#{@authEndpoint}?#{qs}"
     @deferred = new jQuery.Deferred()
@@ -34,7 +100,7 @@

   callback: =>
     hash = @popup.location.hash.slice(1)
-    params = qs = Readmill.utils.parseQueryString(hash)
+    params = qs = utils.parseQueryString(hash)
     @popup.close()

     if params.access_token
@@ -50,7 +116,8 @@
       toolbar: no, location: 1, scrollbars: yes
       top: top, left: left, width:  width, height: height

-    paramString = Readmill.utils.serializeQueryString(params, ",")
+    paramString = utils.serializeQueryString(params, ",")
     window.open url, "readmill-connect", paramString

-window.Annotator.Plugin.Readmill = Readmill
+window.Annotator.Plugin.Readmill = jQuery.extend Readmill,
+  Auth: Auth, Store: Store, Client: Client, utils: utils


ajustado o nome do fieldConfirmation

ajustado o nome do fieldConfirmation

--- app/assets/javascripts/jqvr/built_in_validators.js.coffee
+++ app/assets/javascripts/jqvr/built_in_validators.js.coffee
@@ -1,7 +1,8 @@
 jQuery ->
   # confirmation
   jQuery.validator.addMethod("confirmation", (value,element,params) ->
-    fieldConfirmationNameSelector = getFieldNameSelector(element.name + '_confirmation')
+    fieldConfirmationName=element.name.substring(0,(element.name.length-1)) + '_confirmation]'
+    fieldConfirmationNameSelector = getFieldNameSelector(fieldConfirmationName)
     fieldConfirmation=jQuery(fieldConfirmationNameSelector)[0]

     if fieldConfirmation.value isnt ""
@@ -10,7 +11,7 @@
       true
   )

-  jQuery('[name$="_confirmation"]').blur(->
+  jQuery('[name$="_confirmation]"]').blur(->
     fieldToConfirm=getFieldToConfirm(@)
     isValid=true

@@ -19,7 +20,7 @@
       jQuery(fieldToConfirm).addClass('was_invalid')
   )

-  jQuery('[name$="_confirmation"]').keyup(->
+  jQuery('[name$="_confirmation]"]').keyup(->
     fieldToConfirm= getFieldToConfirm(@)

     if jQuery(fieldToConfirm).hasClass('was_invalid')
@@ -27,7 +28,7 @@
   )

   getFieldToConfirm= (fieldConfirmation) ->
-    fieldToConfirmName= fieldConfirmation.name.substring(0,(fieldConfirmation.name.length-13))
+    fieldToConfirmName= fieldConfirmation.name.substring(0,(fieldConfirmation.name.length-14)) + ']'
     fieldToConfirmNameSelector=getFieldNameSelector(fieldToConfirmName)

     jQuery(fieldToConfirmNameSelector)


Set do not track to false if it doesn't exist

--- src/lib/middleware/locals.coffee
+++ src/lib/middleware/locals.coffee
@@ -11,6 +11,6 @@
   res.locals.homeHref = req.user?.homePath() or '/'

   # Respect do not track headers
-  res.locals.doNotTrack = res.locals.sd.DO_NOT_TRACK = req.headers.dnt
+  res.locals.doNotTrack = res.locals.sd.DO_NOT_TRACK = req.headers.dnt || false

   next()


Delete debug stuff

Delete debug stuff

--- frontend/utils.coffee
+++ frontend/utils.coffee
@@ -22,7 +22,7 @@
 exports.login = login = (username, password)->
   base = global.wolis.config.test_url

-  casper.open base + '/index.php?x'
+  casper.open base

   casper.then ->
     @test.assertHttpStatus 200


skip logic presenter cannot call show() (breaking survey load and possibly save) adding a trackJs error message to help narrow down the problem

--- jsapp/xlform_model_view/mv.skipLogicHelpers.coffee
+++ jsapp/xlform_model_view/mv.skipLogicHelpers.coffee
@@ -327,7 +327,10 @@

     determine_add_new_criterion_visibility: () ->
       if @all_presenters_are_valid()
-        @$add_new_criterion_button.show()
+        if !@$add_new_criterion_button
+          trackJs.console.error("@$add_new_criterion_button is not defined")
+        else
+          @$add_new_criterion_button.show()
       else
         @$add_new_criterion_button.hide()


Only remove the class when the fade is done, to prevent double scrollbars

--- app/js/util/modal.coffee
+++ app/js/util/modal.coffee
@@ -26,8 +26,8 @@
 Factlink.modal =
   hide: ->
     unbindClick()
-    iFrame.fadeOut 'fast'
-    $('html').removeClass ('fl-modal-open')
+    iFrame.fadeOut 'fast', ->
+      $('html').removeClass ('fl-modal-open')

     Factlink.trigger 'modalClosed'