Cleaned up and made spacing consistent

Cleaned up and made spacing consistent

--- static/js/main.coffee
+++ static/js/main.coffee
@@ -1,67 +1,66 @@

 api = (resource) ->
-    'http://node2.storj.io/api/' + resource
+  'http://node2.storj.io/api/' + resource

 GIGABYTE = 1024 * 1024 * 1024

 Cookies = {
-    set: ((k, v, days) ->
-        if days
-            date = new Date()
-            secs = days * 24 * 60 * 60 * 1000
-            date.setTime(date.getTime() + secs)
-            expires = '; expires=' + date.toGMTString() + '; max-age=' + secs
-        else
-            expires = ''
-
-        document.cookie = k + '=' + v + expires + '; path=/'
-        v
-    ),
-
-    get: ((k) ->
-        cookies = document.cookie.split(';')
-        for cookie in cookies
-
-            cookie = cookie.trim()
-            index = cookie.indexOf('=')
-            [name, v] = [cookie.substring(0, index), cookie.substring(index + 1)]
-
-            if name is k
-                return v
-        null
-    ),
-
-    kill: ((k) ->
-        this.set(k, '', -11)
-    )
+  set: ((k, v, days) ->
+    if days
+      date = new Date()
+      secs = days * 24 * 60 * 60 * 1000
+      date.setTime(date.getTime() + secs)
+      expires = '; expires=' + date.toGMTString() + '; max-age=' + secs
+    else
+      expires = ''
+
+    document.cookie = k + '=' + v + expires + '; path=/'
+    v
+  ),
+
+  get: ((k) ->
+    cookies = document.cookie.split(';')
+    for cookie in cookies
+      cookie = cookie.trim()
+      index = cookie.indexOf('=')
+      [name, v] = [cookie.substring(0, index), cookie.substring(index + 1)]
+
+      if name is k
+        return v
+    null
+  ),
+
+  kill: ((k) ->
+    this.set(k, '', -11)
+  )
 }

 LocalStorage = {
-    set: ((k, v) ->
-        localStorage.setItem(k, v)
-        v
-    ),
-
-    get:  ((k) ->
-        localStorage.getItem(k)
-    ),
-
-    kill: ((k) ->
-        localStorage.removeItem(k)
-    )
+  set: ((k, v) ->
+    localStorage.setItem(k, v)
+    v
+  ),
+
+  get:  ((k) ->
+    localStorage.getItem(k)
+  ),
+
+  kill: ((k) ->
+    localStorage.removeItem(k)
+  )
 }

 KeyValueStore = LocalStorage

 History = {
-    add: ((file) ->
-        stuff = this.get()
-        stuff.unshift(file)
-        KeyValueStore.set('history', JSON.stringify(stuff))
-    ),
-    get: (-> JSON.parse(KeyValueStore.get('history')) or []),
-    kill: (-> KeyValueStore.kill('history'))
+  add: ((file) ->
+    stuff = this.get()
+    stuff.unshift(file)
+    KeyValueStore.set('history', JSON.stringify(stuff))
+  ),
+  get: (-> JSON.parse(KeyValueStore.get('history')) or []),
+  kill: (-> KeyValueStore.kill('history'))
 }

 AccessToken = {
@@ -81,340 +80,339 @@

   generate: () ->
     $.post api('token/new'), (data) ->
-        token = KeyValueStore.set('access-token', data['token'])
+      token = KeyValueStore.set('access-token', data['token'])
     .done () ->
-        loadPersonal()
-        loadStats()
-        $.growl
-              title: 'Done!'
-              icon: 'glyphicon glyphicon-ok'
-              message: 'Successfully created a new access token.'
-            ,
-              template:
-                icon_type: 'class'
-                container: '

'
-
-              position:
-                from: 'bottom'
-                align: 'right'
+      loadPersonal()
+      loadStats()
+      $.growl
+        title: 'Done!'
+        icon: 'glyphicon glyphicon-ok'
+        message: 'Successfully created a new access token.'
+      ,
+        template:
+          icon_type: 'class'
+          container: '

'
+
+        position:
+          from: 'bottom'
+          align: 'right'
     .fail () ->
-        $.growl
-              title: 'Whoops!'
-              icon: 'glyphicon glyphicon-remove'
-              message: 'Failed to create a new access token.'
-            ,
-              template:
-                icon_type: 'class'
-                container: '

'
-
-              position:
-                from: 'bottom'
-                align: 'right'
+      $.growl
+        title: 'Whoops!'
+        icon: 'glyphicon glyphicon-remove'
+        message: 'Failed to create a new access token.'
+      ,
+        template:
+          icon_type: 'class'
+          container: '

'
+
+        position:
+          from: 'bottom'
+          align: 'right'
 }

 gigabytes = (bytes) ->
-    (bytes / GIGABYTE).toFixed(2) + ' GB'
+  (bytes / GIGABYTE).toFixed(2) + ' GB'

 gigabytes_ratio = (bytes, total) ->
-    if total is 0
-        (bytes / GIGABYTE).toFixed(2) + '/∞ GB'
-    else
-        (bytes / GIGABYTE).toFixed(2) + '/' + gigabytes(total)
+  if total is 0
+    (bytes / GIGABYTE).toFixed(2) + '/∞ GB'
+  else
+    (bytes / GIGABYTE).toFixed(2) + '/' + gigabytes(total)

 percentage = (bytes, total) ->
-    if total is 0
-        '0%'
-    else
-        (100 * bytes / total) + '%'
+  if total is 0
+    '0%'
+  else
+    (100 * bytes / total) + '%'

 loadPersonal = ->
-    AccessToken.get (token) ->
-        $('#access-token').val(token)
-
-        $.getJSON api('token/balance/' + token), (data) ->
-          $('#token-balance').html(gigabytes(data['balance']))
-          $('#token-estimated-storage').html(gigabytes(data['balance'] / 3.0))
+  AccessToken.get (token) ->
+    $('#access-token').val(token)
+
+    $.getJSON api('token/balance/' + token), (data) ->
+      $('#token-balance').html(gigabytes(data['balance']))
+      $('#token-estimated-storage').html(gigabytes(data['balance'] / 3.0))

 loadStats = ->
-    $.getJSON api('status'), (info) ->
-        $('#cont-file-size-limit').html(gigabytes(info.storage.max_file_size))
-
-        $('#bar-ul-bandwidth').css('width', percentage(info.bandwidth.current.incoming, info.bandwidth.limits.incoming))
-        $('#cont-ul-bandwidth').html(gigabytes_ratio(info.bandwidth.current.incoming, info.bandwidth.limits.incoming))
-
-        $('#bar-dl-bandwidth').css('width', percentage(info.bandwidth.current.outgoing, info.bandwidth.limits.outgoing))
-        $('#cont-dl-bandwidth').html(gigabytes_ratio(info.bandwidth.current.outgoing, info.bandwidth.limits.outgoing))
-
-        $('#bar-storage').css('width', percentage(info.storage.used, info.storage.capacity))
-        $('#cont-storage').text(gigabytes_ratio(info.storage.used, info.storage.capacity))
-
-        $('#cont-datacoin-bal').text(info.datacoin.balance + ' DTC')
-        $('#cont-datacoin-addr')
-            .html('' + info.datacoin.address + '')
-            .find('code').on 'click', ()->
-                selectElementText($(this)[0])
-
-        $('#cont-sync-cloud').text(info.sync.cloud_queue.count + ' (' + gigabytes(info.sync.cloud_queue.size) + ')')
-        $('#cont-sync-blockchain').text(info.sync.blockchain_queue.count + ' (' + gigabytes(info.sync.blockchain_queue.size) + ')')
+  $.getJSON api('status'), (info) ->
+    $('#cont-file-size-limit').html(gigabytes(info.storage.max_file_size))
+
+    $('#bar-ul-bandwidth').css('width', percentage(info.bandwidth.current.incoming, info.bandwidth.limits.incoming))
+    $('#cont-ul-bandwidth').html(gigabytes_ratio(info.bandwidth.current.incoming, info.bandwidth.limits.incoming))
+
+    $('#bar-dl-bandwidth').css('width', percentage(info.bandwidth.current.outgoing, info.bandwidth.limits.outgoing))
+    $('#cont-dl-bandwidth').html(gigabytes_ratio(info.bandwidth.current.outgoing, info.bandwidth.limits.outgoing))
+
+    $('#bar-storage').css('width', percentage(info.storage.used, info.storage.capacity))
+    $('#cont-storage').text(gigabytes_ratio(info.storage.used, info.storage.capacity))
+
+    $('#cont-datacoin-bal').text(info.datacoin.balance + ' DTC')
+    $('#cont-datacoin-addr')
+      .html('' + info.datacoin.address + '')
+      .find('code').on 'click', ()->
+        selectElementText($(this)[0])
+
+    $('#cont-sync-cloud').text(info.sync.cloud_queue.count + ' (' + gigabytes(info.sync.cloud_queue.size) + ')')
+    $('#cont-sync-blockchain').text(info.sync.blockchain_queue.count + ' (' + gigabytes(info.sync.blockchain_queue.size) + ')')

 loadPersonal()
 loadStats()

 showUploadStage = (stage) ->
-    # hide all three
-    $('#cont-upload').hide()
-    $('#cont-uploaded').hide()
-    $('#cont-uploading').hide()
-
-    # show the one we want
-    $('#cont-' + stage).show()
+  # hide all three
+  $('#cont-upload').hide()
+  $('#cont-uploaded').hide()
+  $('#cont-uploading').hide()
+
+  # show the one we want
+  $('#cont-' + stage).show()

 selectElementText = (el, win) ->
-    win = win or window
-    doc = win.document
-
-    if win.getSelection and doc.createRange
-        sel = win.getSelection()
-        range = doc.createRange()
-        range.selectNodeContents(el)
-        sel.removeAllRanges()
-        sel.addRange(range)
-
-    else if doc.body.createTextRange
-        range = doc.body.createTextRange()
-        range.moveToElementText(el)
-        range.select()
+  win = win or window
+  doc = win.document
+
+  if win.getSelection and doc.createRange
+    sel = win.getSelection()
+    range = doc.createRange()
+    range.selectNodeContents(el)
+    sel.removeAllRanges()
+    sel.addRange(range)
+
+  else if doc.body.createTextRange
+    range = doc.body.createTextRange()
+    range.moveToElementText(el)
+    range.select()

 downloadUrl = (file, token) ->
-    queryString = {}
-
-    if file.key
-        queryString['key'] = file.key
-
-    if token
-        queryString['token'] = token
-
-    api('download/' + file.fhash + "?" + $.param(queryString))
+  queryString = {}
+
+  if file.key
+    queryString['key'] = file.key
+
+  if token
+    queryString['token'] = token
+
+  api('download/' + file.fhash + "?" + $.param(queryString))

 addFile = (file) ->
-    $file = $('
')
-        .addClass('file-row cf')
-        .append(
-            $('
')
-                .addClass('left')
-                .append('
' + file.fname + '
')
-                .append('
' + file.fhash + '
')
-        )
-        .append(
-            $('
')
-                .addClass('right')
-                .append('<button class="btn btn-dl">Download</button>')
-        )
-        .append(
-            $('
')
-                .addClass('right')
-                .append('<button class="btn btn-copy-url">Copy URL</button>')
-        )
-        .appendTo($('#cont-file-list'))
-
-    $file.find('button.btn-dl').on 'click', () ->
-        AccessToken.get (token) ->
-          window.location.href = downloadUrl(file, token)
-
-    # Copying file download URL
-    downloadUrlCopy = new ZeroClipboard($file.find('button.btn-copy-url'))
-    downloadUrlCopy.on 'copy', (e) ->
-        e.clipboardData.setData 'text/plain', (AccessToken.get (token) -> return downloadUrl(file, token))
-    downloadUrlCopy.on 'aftercopy', (e) ->
-        if e.success['text/plain']
-            $.growl
-              title: 'Done!'
-              icon: 'glyphicon glyphicon-ok'
-              message: 'Successfully copied the download URL.'
-            ,
-              template:
-                icon_type: 'class'
-                container: '

'
-
-              position:
-                from: 'bottom'
-                align: 'right'
-
-        else
-            $.growl
-              title: 'Whoops!'
-              icon: 'glyphicon glyphicon-remove'
-              message: 'Failed to copy the download URL.'
-            ,
-              template:
-                icon_type: 'class'
-                container: '

'
-
-              position:
-                from: 'bottom'
-                align: 'right'
-
-        return
-
-    # Copying file hash
-    $fileCode = $file.find('code')
-    fileHashCopy = new ZeroClipboard($fileCode)
-    fileHashCopy.on 'copy', (e) ->
-        e.clipboardData.setData 'text/plain', $fileCode.html()
-    fileHashCopy.on 'aftercopy', (e) ->
-        if e.success['text/plain']
-            $.growl
-              title: 'Done!'
-              icon: 'glyphicon glyphicon-ok'
-              message: 'Successfully copied the file hash.'
-            ,
-              template:
-                icon_type: 'class'
-                container: '

'
-
-              position:
-                from: 'bottom'
-                align: 'right'
-
-        else
-            $.growl
-              title: 'Whoops!'
-              icon: 'glyphicon glyphicon-remove'
-              message: 'Failed to copy the file hash.'
-            ,
-              template:
-                icon_type: 'class'
-                container: '

'
-
-              position:
-                from: 'bottom'
-                align: 'right'
-
-        return
+  $file = $('
')
+    .addClass('file-row cf')
+    .append(
+      $('
')
+        .addClass('left')
+        .append('
' + file.fname + '
')
+        .append('
' + file.fhash + '
')
+    )
+    .append(
+      $('
')
+        .addClass('right')
+        .append('<button class="btn btn-dl">Download</button>')
+    )
+    .append(
+      $('
')
+        .addClass('right')
+        .append('<button class="btn btn-copy-url">Copy URL</button>')
+    )
+    .appendTo($('#cont-file-list'))
+
+  $file.find('button.btn-dl').on 'click', () ->
+    AccessToken.get (token) ->
+      window.location.href = downloadUrl(file, token)
+
+  # Copying file download URL
+  downloadUrlCopy = new ZeroClipboard($file.find('button.btn-copy-url'))
+  downloadUrlCopy.on 'copy', (e) ->
+    e.clipboardData.setData 'text/plain', (AccessToken.get (token) -> return downloadUrl(file, token))
+  downloadUrlCopy.on 'aftercopy', (e) ->
+    if e.success['text/plain']
+      $.growl
+        title: 'Done!'
+        icon: 'glyphicon glyphicon-ok'
+        message: 'Successfully copied the download URL.'
+      ,
+        template:
+          icon_type: 'class'
+          container: '

'
+
+        position:
+          from: 'bottom'
+          align: 'right'
+    else
+      $.growl
+        title: 'Whoops!'
+        icon: 'glyphicon glyphicon-remove'
+        message: 'Failed to copy the download URL.'
+      ,
+        template:
+          icon_type: 'class'
+          container: '

'
+
+        position:
+          from: 'bottom'
+          align: 'right'
+
+    return
+
+  # Copying file hash
+  $fileCode = $file.find('code')
+  fileHashCopy = new ZeroClipboard($fileCode)
+  fileHashCopy.on 'copy', (e) ->
+    e.clipboardData.setData 'text/plain', $fileCode.html()
+  fileHashCopy.on 'aftercopy', (e) ->
+    if e.success['text/plain']
+      $.growl
+        title: 'Done!'
+        icon: 'glyphicon glyphicon-ok'
+        message: 'Successfully copied the file hash.'
+      ,
+        template:
+          icon_type: 'class'
+          container: '

'
+
+        position:
+          from: 'bottom'
+          align: 'right'
+
+    else
+      $.growl
+        title: 'Whoops!'
+        icon: 'glyphicon glyphicon-remove'
+        message: 'Failed to copy the file hash.'
+      ,
+        template:
+          icon_type: 'class'
+          container: '

'
+
+        position:
+          from: 'bottom'
+          align: 'right'
+
+    return

 makeHandler = (fname) ->
-    (response) ->
-        loadPersonal()
-
-        file = {fname: fname, fhash: response.filehash, key: response.key}
-        History.add(file)
-
-        showUploadStage('uploaded')
-        AccessToken.get (token) -> $('#span-dl-link').val(downloadUrl(file, token))
-
-        page = currentPage()
-        initFilePages()
-        pickFilePage(page)
-        loadStats()
+  (response) ->
+    loadPersonal()
+
+    file = {fname: fname, fhash: response.filehash, key: response.key}
+    History.add(file)
+
+    showUploadStage('uploaded')
+    AccessToken.get (token) -> $('#span-dl-link').val(downloadUrl(file, token))
+
+    page = currentPage()
+    initFilePages()
+    pickFilePage(page)
+    loadStats()

 # when a file is selected
 $('#in-upload').on 'change', () ->
-    uploadFiles(this.files)
+  uploadFiles(this.files)

 uploadFiles = (files) ->
-    AccessToken.get (token) ->
-        for file in files
-            uploadFile(token, file)
+  AccessToken.get (token) ->
+    for file in files
+      uploadFile(token, file)

 uploadFile = (token, file) ->
-    fname = file.name
-
-    formData = new FormData()
-    formData.append('token', token)
-    formData.append('file', file)
-
-    showUploadStage('uploading')
-
+  fname = file.name
+
+  formData = new FormData()
+  formData.append('token', token)
+  formData.append('file', file)
+
+  showUploadStage('uploading')
+
+  $('#span-up-prog')
+    .css('width', '0%')
+    .text('0%')
+
+  progressHandler = (e) ->
+    perc = (e.loaded / e.total * 100)
     $('#span-up-prog')
-        .css('width', '0%')
-        .text('0%')
-
-    progressHandler = (e) ->
-        perc = (e.loaded / e.total * 100)
-        $('#span-up-prog')
-            .css('width', perc + '%')
-            .text(Math.round(perc) + '%')
-
-    $.ajax {
-        url: api('upload'),
-        type: 'POST',
-        xhr: (->
-            xhr = $.ajaxSettings.xhr()
-            if xhr.upload
-                xhr.upload.addEventListener('progress', progressHandler, false)
-            xhr
-        ),
-        data: formData,
-        cache: false,
-        contentType: false,
-        processData: false,
-        success: makeHandler(fname),
-    }
+      .css('width', perc + '%')
+      .text(Math.round(perc) + '%')
+
+  $.ajax {
+    url: api('upload'),
+    type: 'POST',
+    xhr: (->
+      xhr = $.ajaxSettings.xhr()
+      if xhr.upload
+        xhr.upload.addEventListener('progress', progressHandler, false)
+      xhr
+    ),
+    data: formData,
+    cache: false,
+    contentType: false,
+    processData: false,
+    success: makeHandler(fname),
+  }

 # Drag and drop support
 $('body').on('dragenter',
   ((e) ->
-      e.preventDefault()
-      e.stopPropagation()))
+    e.preventDefault()
+    e.stopPropagation()))

 $('body').on('dragover',
   ((e) ->
-      e.preventDefault()
-      e.stopPropagation()))
+    e.preventDefault()
+    e.stopPropagation()))

 $('body').on('drop',
   ((e) ->
-      uploadFiles(e.originalEvent.dataTransfer.files)
-
-      e.preventDefault()
-      e.stopPropagation()))
+    uploadFiles(e.originalEvent.dataTransfer.files)
+
+    e.preventDefault()
+    e.stopPropagation()))

 redeem = (promocode, token) ->
-    $.ajax({
-      type:        'POST',
-      url:         api('token/redeem/' + token),
-      data:        JSON.stringify({ promocode: promocode }),
-      contentType: 'application/json; charset=utf-8'
-      success:     (-> loadPersonal())
-    })
+  $.ajax({
+    type:        'POST',
+    url:         api('token/redeem/' + token),
+    data:        JSON.stringify({ promocode: promocode }),
+    contentType: 'application/json; charset=utf-8'
+    success:     (-> loadPersonal())
+  })

 # Redeeming
 $('#redeem-promocode').on 'click', () ->
-    AccessToken.get (token) ->
-        redeem($('#promocode').val(), token)
+  AccessToken.get (token) ->
+    redeem($('#promocode').val(), token)

 $('#say-please').on 'click', () ->
-    AccessToken.get (token) ->
-        redeem('PLEASE', token)
+  AccessToken.get (token) ->
+    redeem('PLEASE', token)

 # Searching
 $('.searchbox input[name=search]').on 'keypress', (e) ->
-    if e.which == 13
-        window.location.href = api('download/' + $(e.target).val())
+  if e.which == 13
+    window.location.href = api('download/' + $(e.target).val())

 # select the link when the user focuses or clicks
 $('#span-dl-link').on 'focus', () -> $(this).select()
 $('#span-dl-link').on 'click', () -> $(this).select()

 $('#btn-upload-another').on 'click', () ->
-    showUploadStage('upload')
+  showUploadStage('upload')

 # Refresh/get new access token
 $('#access-token-refresh').on 'click', () ->
-    AccessToken.generate()
+  AccessToken.generate()

 # Re-enable access token field
 $('#access-token-edit').on 'click', () ->
-    $('#access-token').attr('disabled', false)
+  $('#access-token').attr('disabled', false)

 # Update token info when enabled token field loses focus
-$('#access-token').on 'keyup blur', (e) ->
-    if e.type == 'blur' or (e.type == 'keyup' and e.keyCode == 13)
-        $('#access-token').attr('disabled', true)
-        AccessToken.set($('#access-token').val())
+$('#access-token').on 'keyup blur', (e) ->
+  if e.type == 'blur' or (e.type == 'keyup' and e.keyCode == 13)
+    $('#access-token').attr('disabled', true)
+    AccessToken.set($('#access-token').val())

 # Copy Access Token on 'Copy'
 accessTokenCopy = new ZeroClipboard($('#access-token-copy'))
@@ -437,7 +435,6 @@
       position:
         from: 'bottom'
         align: 'right'
-
   else
     $.growl
       title: 'Whoops!'
@@ -451,58 +448,56 @@
       position:
         from: 'bottom'
         align: 'right'
-
   return

 # load history
-
 files = History.get()

 currentPage = ->
-    return parseInt($('#cont-pagination').attr('data-current')) | 0
+  return parseInt($('#cont-pagination').attr('data-current')) | 0

 pageCount = ->
-    return parseInt((History.get().length + 9) / 10)
+  return parseInt((History.get().length + 9) / 10)

 pickPagination = (page) ->
-    $('#cont-pagination').attr('data-current', page)
-    $('#cont-pagination button').each ->
-        found = false
-        if not isNaN($(this).attr('data-id'))
-            if parseInt($(this).attr('data-id')) is page
-                found = true
-
-        $(this).prop('disabled', found)
+  $('#cont-pagination').attr('data-current', page)
+  $('#cont-pagination button').each ->
+    found = false
+    if not isNaN($(this).attr('data-id'))
+      if parseInt($(this).attr('data-id')) is page
+        found = true
+
+    $(this).prop('disabled', found)

 pickFilePage = (page) ->
-    if page is "next"
-        page = Math.min(currentPage() + 1, pageCount()-1)
-
-    if page is "prev"
-        page = Math.max(0, currentPage() - 1)
-
-    page = parseInt(page)
-
-    $('#cont-file-list').empty()
-    for file in History.get()[page * 10...(page + 1) * 10]
-        addFile(file)
-
-    pickPagination(page)
+  if page is "next"
+    page = Math.min(currentPage() + 1, pageCount()-1)
+
+  if page is "prev"
+    page = Math.max(0, currentPage() - 1)
+
+  page = parseInt(page)
+
+  $('#cont-file-list').empty()
+  for file in History.get()[page * 10...(page + 1) * 10]
+    addFile(file)
+
+  pickPagination(page)

 initFilePages = ->
-    $cont = $('#cont-pagination')
-    $cont.empty()
-
-    $cont.append('<button data-id="prev" type="button" class="btn btn-default"></button>')
-
-    for i in [0...pageCount()]
-        $cont.append('<button data-id="' + i + '" type="button" class="btn btn-default">' + (i + 1) + '</button>')
-
-    $cont.append('<button data-id="next" type="button" class="btn btn-default"></button>')
-
-    $cont.find('button').on 'click', () ->
-        pickFilePage($(this).attr('data-id'))
-    pickFilePage(0)
-
-initFilePages()
+  $cont = $('#cont-pagination')
+  $cont.empty()
+
+  $cont.append('<button data-id="prev" type="button" class="btn btn-default"></button>')
+
+  for i in [0...pageCount()]
+    $cont.append('<button data-id="' + i + '" type="button" class="btn btn-default">' + (i + 1) + '</button>')
+
+  $cont.append('<button data-id="next" type="button" class="btn btn-default"></button>')
+
+  $cont.find('button').on 'click', () ->
+    pickFilePage($(this).attr('data-id'))
+  pickFilePage(0)
+
+initFilePages()


Fixed rawText commands usesRemaining not being incremented when they are deleted

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -544,9 +544,13 @@
         if currentRow >= maxRow - @codeSuffixLength or currentRow < @codePrefixLength
             return
         line = text.getLine currentRow
-        command = @interpreter.identifyCommand line
-        if command?
-            @commands[command]['usesRemaining']++
+
+        if @commands.hasOwnProperty line.trim()
+            @commands[line.trim()]['usesRemaining']++
+        else
+            command = @interpreter.identifyCommand line
+            if command?
+                @commands[command]['usesRemaining']++
         if text.getLength() == 1
             text.insertLines currentRow + 1, ["\n"]
             text.removeNewLine currentRow


insertLine now works correctly with uneditable lines, added semicolons to the end of inserted lines

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -169,7 +169,7 @@
         buttonField = jQuery('#insertButtons')
         buttons = []
         for command of @commands
-            line = @createBlankFunctionHeader(command)
+            line = @createBlankFunctionHeader(command) + ';'
             usesRemaining = @commands[command]['usesRemaining']
             codeEditor = @
             button = jQuery '<button>', {
@@ -341,7 +341,8 @@

     insertLine: ({text, line, currentRow}) ->
         maxRow = @editSession.getLength()
-        if currentRow + 1 < @codePrefixLength or currentRow + 1 >= maxRow - @codeSuffixLength
+        # if currentRow + 1 < @codePrefixLength # or currentRow + 1 >= maxRow - @codeSuffixLength
+        if currentRow + 1 < @codePrefixLength or currentRow + 1 >= maxRow - (@codeSuffixLength - 1)
             return
         inputsDiv = jQuery('#insertButtons')


Update pt-BR.coffee

Update pt-BR.coffee

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -1084,9 +1084,9 @@
     signup_with: "Entrar com:"
     connect_with: "Conectar - se com:"
     conversion_warning: "AVISO: Sua conta atual é uma Conta de Estudante. Ao submeter esse formulário, sua conta será convertida em uma Conta de Professor."
-#    learn_more_modal: "Teacher accounts on CodeCombat have the ability to monitor student progress, assign licenses and manage classrooms. Teacher accounts cannot be a part of a classroom - if you are currently enrolled in a class using this account, you will no longer be able to access it once you update to a Teacher Account."
-#    create_account: "Create a Teacher Account"
-#    create_account_subtitle: "Get access to teacher-only tools for using CodeCombat in the classroom.  Set up a class, add your students, and monitor their progress!"
+    learn_more_modal: "As contas de professores no CodeCombat têm a capacidade de monitorar o progresso do aluno, atribuir licenças e gerenciar salas de aula. As contas de professor não podem fazer parte de uma sala de aula - se você estiver matriculado em uma turma usando essa conta, não poderá mais acessá-la depois de atualizar para uma conta do professor."
+    create_account: "Crie uma conta de professor"
+    create_account_subtitle: "Tenha acesso a ferramentas exclusivas de professores para usar o CodeCombat na sala de aula.  Configure uma turma, adicione seus alunos, e monitore seu progresso.!"
     convert_account_title: "Atualizar para conta de professor"
     not: "Não"


Refactor Level.addEntity()

Refactor Level.addEntity()

--- app/models/Level.coffee
+++ app/models/Level.coffee
@@ -162,7 +162,9 @@
           @player = obj

   addEntity: (object) =>
-    @entityObjects.push @createEntity object
+    obj = @createEntity object
+    @entityObjects.push obj
+    return obj

   createEntity: (object) =>
     # get constructor and json config


cuts the loop out of blends on 404

cuts the loop out of blends on 404

--- providers/blend.coffee
+++ providers/blend.coffee
@@ -73,4 +73,5 @@
 		c !!a
 	async.map @layers, mapFunc, (err, tilesRaw)->
 		async.filter tilesRaw, filterFunc, (tiles)->
-			composit tiles, cb+			composit tiles, cb if tiles.length>1
+			cb null, tiles[0] if tiles.length==1


Fixing server again

Fixing server again

It works!!

--- src/prerenderer/prerenderer.coffee
+++ src/prerenderer/prerenderer.coffee
@@ -12,7 +12,7 @@
     fs.createReadStream 'resources/grassland_tiles.png'
       .pipe new PNG filterType: 4
       .on 'parsed', ->
-        tile.copy @, 1, 1, section, 1, 1
+        tile.addTile @, 1, 1, section, 1, 1
         section.pack().pipe(response)

 module.exports = Prerenderer


remove obsolete return statement

remove obsolete return statement

--- app/assets/javascripts/common-helpers.js.coffee
+++ app/assets/javascripts/common-helpers.js.coffee
@@ -4,8 +4,6 @@
     env.userId.length == 0

   setupInterface: ->
-    return if app.noLogin()
-
     ($ '#agent_overview > h5').click ->
       ($ '#call_queue').toggleClass('expanded')


Revert "gqueries: force-converting invalid values to 0 is rarely good"

Some charts were broken.
TODO: refactor gquery.coffee and chart_serie.coffee

--- app/assets/javascripts/lib/models/gquery.coffee
+++ app/assets/javascripts/lib/models/gquery.coffee
@@ -6,6 +6,11 @@
   result : ->
     present_value = @get('present_value')
     future_value = @get('future_value')
+
+    if !@is_acceptable_value(present_value) && !@is_acceptable_value(future_value)
+      # console.warn "Gquery #{@get('key')}: #{present_value}/#{future_value}, reset to 0"
+      present_value = 0
+      future_value  = 0

     [ [@get('present_year'), present_value], [@get('future_year'), future_value] ]


Use single method

Use single method

--- app/assets/javascripts/modules/router.js.coffee
+++ app/assets/javascripts/modules/router.js.coffee
@@ -1,5 +1,6 @@
 class Router
   defaultConfig: {
+    controller: 'GenericController',
     controllerAs: 'controller'
   }
   directRoutes: [
@@ -14,25 +15,19 @@
     this.provider = $routeProvider

   bindRoutes: () ->
-    router = this
+    _.each(this.directRoutes, this._setRouteConfig)
+    _.each(this.customRoutes, this._setRouteConfig)

-    _.each(router.directRoutes, (route) ->
-      router.provider.when(route, {
-        templateUrl: router.buildTemplateFor(route),
-        controller: 'GenericController',
-        controllerAs: 'controller'
-      })
-    )
+  _setRouteConfig: (config, route) =>
+    if (typeof route != 'string')
+      route = config
+      config = {}

-    _.each(router.customRoutes, router._setRouteConfig)
+    config = _.extend({}, this.defaultConfig, {
+      templateUrl: this.buildTemplateFor(route)
+    }, config)

-  _setRouteConfig: (params, route) =>
-    router = this
-    params = _.extend({}, router.defaultConfig, {
-      templateUrl: router.buildTemplateFor(route)
-    }, params)
-
-    router.provider.when(route, params)
+    this.provider.when(route, config)

   buildTemplateFor: (route)->
     (params) ->


assignee bugfix

assignee bugfix

--- app/assets/javascripts/projects/tasks.js.coffee
+++ app/assets/javascripts/projects/tasks.js.coffee
@@ -130,11 +130,9 @@
     if found.length > 0 then found[0].username else null

   canChangeAssignee: (task) ->
-    resu = true
+    resu = false
     logged = task.work_logged
-    if logged != 0
-      resu = true
-    else
+    if logged == 0
       if gon.can_take_unassigned_task && !gon.can_update_tasks
         resu = task.assignee_id == "" or task.assignee_id == null or parseInt(task.assignee_id) == gon.user_id
       else
@@ -694,7 +692,7 @@
       $('#tasks_delta').addClass('bg-red')
     else if delta > 0
       $('#tasks_delta').addClass('bg-green')
-
+
     estimate  = Duration.stringify(estimate, {format: 'micro'}) if estimate != 0
     remaining = Duration.stringify(remaining, {format: 'micro'}) if remaining != 0
     logged    = Duration.stringify(logged, {format: 'micro'}) if logged != 0


capitalize globals; pass named parameters to `grid`

--- intervals.coffee
+++ intervals.coffee
@@ -37,11 +37,11 @@
 #
 # Fretboard
 #
-strings = [1..6]
-string_count = strings.length
-
-frets = [0..4]  # includes nut
-fret_count = frets.length - 1  # doesn't include nut
+StringNumbers = [1..6]
+StringCount = StringNumbers.length
+
+FretNumbers = [0..4]  # includes nuNumbert
+FretCount = FretNumbers.length - 1  # doesn't include nuNumbert

 StringIntervals = [5, 5, 5, 4, 5]
 StringNoteNumbers = (->
@@ -54,8 +54,8 @@
   StringNoteNumbers[string - 1] + fret

 finger_positions_each = (fn) ->
-  for string in strings
-    for fret in frets
+  for string in StringNumbers
+    for fret in FretNumbers
       fn string: string, fret: fret

 intervals_from = (fingering, semitones) ->
@@ -65,6 +65,7 @@
     return unless (fingering_note_number(fingering) - root_note_number + 120) % 12 == semitones
     fingerings.push fingering
   return fingerings
+

 #
 # Drawing
@@ -97,7 +98,8 @@
     filename = "test.png"
     fs.writeFile build_directory + filename, canvas.toBuffer()

-grid = (cols, rows, cell_width, cell_height, header_height, draw_page) ->
+grid = ({cols, rows, cell_width, cell_height, header_height}, draw_page) ->
+  header_height ||= 0
   page cols * cell_width, header_height + rows * cell_height, (ctx) ->
     i = 0
     draw_page (draw_cell) ->
@@ -124,8 +126,8 @@
 string_spacing = 20
 fret_width = 45
 fret_overhang = .3 * fret_width
-padded_fretboard_width = 2 * v_gutter + fret_width * fret_count + fret_overhang
-padded_fretboard_height = 2 * h_gutter + (string_count - 1) * string_spacing
+padded_fretboard_width = 2 * v_gutter + fret_width * FretCount + fret_overhang
+padded_fretboard_height = 2 * h_gutter + (StringCount - 1) * string_spacing

 #
 # Drawing Fretboard and Diagrams
@@ -139,24 +141,24 @@
   h_gutter = 5
   v_gutter = 5
   note_radius = 1
-  padded_fretboard_width = 2 * h_gutter + (string_count - 1) * string_spacing
-  padded_fretboard_height = 2 * v_gutter + fret_height * fret_count + fret_overhang
+  padded_fretboard_width = 2 * h_gutter + (StringCount - 1) * string_spacing
+  padded_fretboard_height = 2 * v_gutter + fret_height * FretCount + fret_overhang

 draw_strings = ->
-  for n, i in strings
+  for n, i in StringNumbers
     y = i * string_spacing + h_gutter
     ctx.beginPath()
     ctx.moveTo h_gutter, y
-    ctx.lineTo h_gutter + fret_count * fret_width + fret_overhang, y
+    ctx.lineTo h_gutter + FretCount * fret_width + fret_overhang, y
     ctx.lineWidth = 1
     ctx.stroke()

 draw_frets = ->
-  for fret_number in frets
+  for fret_number in FretNumbers
     x = fret_number * fret_width + h_gutter
     ctx.beginPath()
     ctx.moveTo x, h_gutter
-    ctx.lineTo x, h_gutter + (string_count - 1) * string_spacing
+    ctx.lineTo x, h_gutter + (StringCount - 1) * string_spacing
     ctx.lineWidth = 3 if fret_number == 0
     ctx.stroke()
     ctx.lineWidth = 1
@@ -177,19 +179,19 @@

 if draw_diagrams
   draw_strings = ->
-    for n, i in strings
+    for n, i in StringNumbers
       x = i * string_spacing + h_gutter
       ctx.beginPath()
       ctx.moveTo x, v_gutter
-      ctx.lineTo x, v_gutter + fret_count * fret_height + fret_overhang
+      ctx.lineTo x, v_gutter + FretCount * fret_height + fret_overhang
       ctx.stroke()

   draw_frets = ->
-    for fret_number in frets
+    for fret_number in FretNumbers
       y = fret_number * fret_height + v_gutter
       ctx.beginPath()
       ctx.moveTo v_gutter-.5, y
-      ctx.lineTo v_gutter+.5 + (string_count - 1) * string_spacing, y
+      ctx.lineTo v_gutter+.5 + (StringCount - 1) * string_spacing, y
       ctx.lineWidth = 3 if fret_number == 0
       ctx.stroke()
       ctx.lineWidth = 1
@@ -214,7 +216,7 @@
     for fingering in fingerings
       fretted_strings[fingering.string] = true
       draw_fingering fingering, fingering
-    for string_number in strings
+    for string_number in StringNumbers
       continue if fretted_strings[string_number]
       ctx.font = '4pt Helvetica'
       ctx.fillStyle = 'black'
@@ -254,7 +256,10 @@
 intervals_from_position_page = (fingering) ->
   canvas_gutter = 20
   header_height = 20
-  grid 3, 4, padded_fretboard_width + canvas_gutter, padded_fretboard_height + header_height, 0, (cell) ->
+  grid {cols: 3, rows: 4
+  , cell_width: padded_fretboard_width + canvas_gutter
+  , cell_height: padded_fretboard_height + header_height}
+  , (cell) ->
     for interval_name, semitones in Intervals
       cell ->
         ctx.translate 0, header_height
@@ -275,14 +280,14 @@
 intervals_page = (semitones) ->
   canvas_gutter = 5
   header_height = 40
-  cols = fret_count + 1
-  rows = string_count
-
-  grid cols, rows
-  , padded_fretboard_width + canvas_gutter
-  , padded_fretboard_height + canvas_gutter
+  cols = FretCount + 1
+  rows = StringCount
+
+  grid {cols, rows
+  , cell_width: padded_fretboard_width + canvas_gutter
+  , cell_height: padded_fretboard_height + canvas_gutter
   , header_height
-  , (cell) ->
+  }, (cell) ->
     title = LongIntervalNames[semitones] + " Intervals"
     ctx.font = '25px Impact'
     ctx.fillStyle = 'rgb(128, 128, 128)'
@@ -328,11 +333,11 @@
   colors = ['red', 'blue', 'green', 'orange']
   other_colors = ['rgba(255,0,0 ,.1)', 'rgba(0,0,255, 0.1)', 'rgba(0,255,0, 0.1)', 'rgba(255,0,255, 0.1)']

-  grid cols, rows
-  , padded_fretboard_width + diagram_gutter
-  , padded_fretboard_height + diagram_gutter
-  , diagram_title_height
-  , (cell) ->
+  grid {cols, rows
+  , cell_width: padded_fretboard_width + diagram_gutter
+  , cell_height: padded_fretboard_height + diagram_gutter
+  , header_height: diagram_title_height
+  }, (cell) ->
     ctx.font = '20px Impact'
     ctx.fillStyle = 'rgb(128, 128, 128)'
     ctx.fillText "#{chord.name} Chords", diagram_gutter / 2, header_height / 2
@@ -353,7 +358,7 @@
         draw_fingerboard fingerings

 optimize_fingers = (fingerings) ->
-  bystring = ([] for _ in strings)
+  bystring = ([] for _ in StringsNumber)
   bynote = ([] for _ in [0..10])
   for fingering in fingerings
     bystring[fingering.string - 1].push fingering
@@ -365,7 +370,7 @@
   fingerings = (f for f in fingerings when f == bass_root or f.string < bass_root.string)
   # Select the lowest fret per string
   # TODO only if each note is represented
-  for string in strings
+  for string in StringNumbers
     fret = (f.fret for f in fingerings when f.string == string).sort()[0]
     fingerings = (f for f in fingerings when f.string != string or f.fret == fret)
   return fingerings


Only show first 6 of user favorite/ranked beatmaps

And then add show more button. Fixes #974.

--- resources/assets/coffee/react/profile-page/beatmaps.coffee
+++ resources/assets/coffee/react/profile-page/beatmaps.coffee
@@ -22,6 +22,12 @@
 ProfilePage.Beatmaps = React.createClass
   mixins: [React.addons.PureRenderMixin]

+
+  getInitialState: ->
+    visible_favourite: 6
+    visible_ranked_and_approved: 6
+
+
   render: ->
     allBeatmapsets =
       favourite: @props.favouriteBeatmapsets
@@ -30,17 +36,35 @@
     div
       className: 'page-extra'
       el ProfilePage.ExtraHeader, name: @props.name, withEdit: @props.withEdit
-      _.map allBeatmapsets, (beatmapsets, section) =>
+      for own section, beatmapsets of allBeatmapsets
         div
           key: section
-          h3 className: 'page-extra__title--small', osu.trans("users.show.extra.beatmaps.#{section}.title", count: beatmapsets.length)
-          if beatmapsets.length
-            div className: 'beatmap-container',
-              div className: 'listing osu-layout__col-container osu-layout__col-container--with-gutter',
-                beatmapsets.map (beatmapset) =>
-                  div
-                    key: beatmapset.id
-                    className: 'osu-layout__col osu-layout__col--sm-6'
-                    el BeatmapsetPanel, beatmap: beatmapset
+          h3
+            className: 'page-extra__title page-extra__title--small'
+            osu.trans("users.show.extra.beatmaps.#{section}.title", count: beatmapsets.length)
+
+          if beatmapsets.length > 0
+            div className: 'osu-layout__col-container osu-layout__col-container--with-gutter',
+              for beatmapset in beatmapsets.slice(0, @state["visible_#{section}"])
+                div
+                  key: beatmapset.id
+                  className: 'osu-layout__col osu-layout__col--sm-6'
+                  el BeatmapsetPanel, beatmap: beatmapset
+
+              if beatmapsets.length > @state["visible_#{section}"]
+                div
+                  className: 'osu-layout__col text-center',
+                  a
+                    href: '#'
+                    onClick: @_showMore
+                    'data-section': section
+                    osu.trans('common.buttons.show_more')
           else
             p className: 'page-extra-entries', osu.trans('users.show.extra.beatmaps.none')
+
+
+  _showMore: (e) ->
+    e.preventDefault()
+
+    key = e.currentTarget.dataset.section
+    @setState "visible_#{key}": (@state["visible_#{key}"] + 10)


added edge-case test to bump code coverage

--- test/loading-bar-interceptor.coffee
+++ test/loading-bar-interceptor.coffee
@@ -98,6 +98,23 @@
     $httpBackend.verifyNoOutstandingRequest()
     $timeout.flush() # loading bar is animated, so flush timeout

+  it 'should not cache when the request is a POST', inject (cfpLoadingBar) ->
+    $httpBackend.expectPOST(endpoint).respond response
+    $http.post(endpoint, {message: 'post'}).then (data) ->
+      result = data
+    expect(cfpLoadingBar.status()).toBe 0
+    $httpBackend.flush(1)
+    expect(cfpLoadingBar.status()).toBe 1
+    $timeout.flush()
+
+
+    $httpBackend.expectPOST(endpoint).respond response
+    $http.post(endpoint, {message: 'post'}).then (data) ->
+      result = data
+    expect(cfpLoadingBar.status()).toBe 0
+    $httpBackend.flush()
+    expect(cfpLoadingBar.status()).toBe 1
+    $timeout.flush()

   it 'should increment the loading bar when not all requests have been recieved', inject (cfpLoadingBar) ->
     $httpBackend.expectGET(endpoint).respond response


VM Creation now works

VM Creation now works

--- src/jobs/instances-insert/job.coffee
+++ src/jobs/instances-insert/job.coffee
@@ -26,19 +26,24 @@
   do: ({data}, callback) =>
     return callback @_userError(422, 'data.projectname is required') unless data.projectname?

+#   only needed for network creation in an insterted instancename
+    zone = data.zonename
+    zonenetwork = zone
+    zonenetwork = zonenetwork.substr(0, zone?.length - 2)
+
     body = {
-      "name": "instance-401",
-      "machineType": "projects/tokeshi-net-izen/zones/us-central1-f/machineTypes/f1-micro",
+      "name": "#{data.instancename}",
+      "machineType": "projects/tokeshi-net-izen/zones/#{data.zonename}/machineTypes/f1-micro",
       "disks": [
         {
           "type": "PERSISTENT",
           "boot": true,
           "mode": "READ_WRITE",
           "autoDelete": true,
-          "deviceName": "instance-401",
+          "deviceName": "instance-500",
           "initializeParams": {
             "sourceImage": "projects/debian-cloud/global/images/debian-9-stretch-v20170829",
-            "diskType": "projects/tokeshi-net-izen/zones/us-central1-f/diskTypes/pd-standard",
+            "diskType": "projects/tokeshi-net-izen/zones/#{data.zonename}/diskTypes/pd-standard",
             "diskSizeGb": "10"
           }
         }
@@ -46,7 +51,7 @@
       "networkInterfaces": [
         {
           "network": "projects/tokeshi-net-izen/global/networks/default",
-          "subnetwork": "projects/tokeshi-net-izen/regions/us-central1/subnetworks/default",
+          "subnetwork": "projects/tokeshi-net-izen/regions/#{zonenetwork}/subnetworks/default",
           "accessConfigs": [
             {
               "name": "External NAT",


envify options are deleted out and checked in other tests

--- test/browserify-tests.coffee
+++ test/browserify-tests.coffee
@@ -3,13 +3,9 @@
   Result.transforms = {}

 defaultOptions = ->
-  return options =
-    basedir:'/full/path/to/app/packages/.npm/package'
-    debug:true
-    transforms:
-      envify:
-        NODE_ENV: 'development'
-        _:'purge'
+  basedir:'/full/path/to/app/packages/.npm/package'
+  debug:true
+  transforms:{}

 # test the usual successful run
 Tinytest.add 'test processFile', (test) ->


Check if rich text is enabled before using stored mode

--- services/web/public/coffee/ide/editor/EditorManager.coffee
+++ services/web/public/coffee/ide/editor/EditorManager.coffee
@@ -6,7 +6,7 @@
 	"ide/editor/controllers/SavingNotificationController"
 ], (Document) ->
 	class EditorManager
-		constructor: (@ide, @$scope, localStorage) ->
+		constructor: (@ide, @$scope, @localStorage) ->
 			@$scope.editor = {
 				sharejs_doc: null
 				open_doc_id: null
@@ -14,7 +14,7 @@
 				opening: true
 				trackChanges: false
 				wantTrackChanges: false
-				showRichText: localStorage("editor.mode.#{@$scope.project_id}") == 'rich-text'
+				showRichText: @showRichText()
 			}

 			@$scope.$on "entity:selected", (event, entity) =>
@@ -40,6 +40,11 @@
 			@$scope.$watch "editor.wantTrackChanges", (value) =>
 				return if !value?
 				@_syncTrackChangesState(@$scope.editor.sharejs_doc)
+
+		showRichText: () ->
+			if !window.richTextEnabled
+				return false
+			@localStorage("editor.mode.#{@$scope.project_id}") == 'rich-text'

 		autoOpenDoc: () ->
 			open_doc_id =


Add test for pastes with no clipboardData

--- test/src/system/pasting_test.coffee
+++ test/src/system/pasting_test.coffee
@@ -1,4 +1,4 @@
-{assert, clickToolbarButton, createFile, defer, expandSelection, moveCursor, pasteContent, pressKey, test, testGroup, typeCharacters} = Trix.TestHelpers
+{assert, clickToolbarButton, createFile, defer, expandSelection, moveCursor, pasteContent, pressKey, test, testGroup, triggerEvent, typeCharacters} = Trix.TestHelpers

 testGroup "Pasting", template: "editor_empty", ->
   test "paste plain text", (expectDocument) ->
@@ -174,3 +174,10 @@
     typeCharacters "a", ->
       pasteContent "Files", (createFile()), ->
         expectDocument "a#{Trix.OBJECT_REPLACEMENT_CHARACTER}\n"
+
+  test "paste event with no clipboardData", (expectDocument) ->
+    typeCharacters "a", ->
+      triggerEvent(document.activeElement, "paste")
+      document.activeElement.insertAdjacentHTML("beforeend", "bc")
+      requestAnimationFrame ->
+        expectDocument("abc\n")


forgot a .only

forgot a .only

--- test/cron_events_test.coffee
+++ test/cron_events_test.coffee
@@ -485,7 +485,7 @@
           room.robot.brain.data.cron = { }
           room.robot.cron.jobs = { }

-      context.only 'and user is not admin', ->
+      context 'and user is not admin', ->
         hubot 'cron stop somejob', 'user'
         it 'should deny permission to the user', ->
           expect(hubotResponse()).to.eql "@user You don't have permission to do that."


sql tests work

sql tests work

--- test/driver/sqlDriver.coffee
+++ test/driver/sqlDriver.coffee
@@ -18,7 +18,7 @@
       return

     emptyDriver = sqlDriver({
-      requester: nullRequester
+      requester: emptyRequester
       table: 'blah'
       filters: null
     })
@@ -65,21 +65,25 @@
     withFilter = sqlDriver({
       requester: sqlPass
       table: 'diamonds'
-      filter: { type: 'is', attribute: 'color', value: 'E' }
+      filter: FacetFilter.fromSpec({
+        type: 'is'
+        attribute: 'color'
+        value: 'E'
+      })
     })

     it "should get back the same result", (done) ->
       noFilter {
-        query: [
+        query: new FacetQuery([
           { operation: 'filter', type: 'is', attribute: 'color', value: 'E' }
           { operation: 'apply', name: 'Count', aggregate: 'count' }
-        ]
+        ])
       }, (err, noFilterRes) ->
         expect(noFilterRes).to.be.an('object')
         withFilter {
-          query: [
+          query: new FacetQuery([
             { operation: 'apply', name: 'Count', aggregate: 'count' }
-          ]
+          ])
         }, (err, withFilterRes) ->
           expect(withFilterRes).to.be.an('object')
           expect(noFilterRes).to.deep.equal(withFilterRes)


cleaner custom device fix

cleaner custom device fix

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -481,7 +481,7 @@
 		@_update()
 		@emit("change:orientation", window.orientation)

-	isPortrait: -> Math.abs(@_orientation) is 0 or !@_orientation?
+	isPortrait: -> Math.abs(@orientation) is 0
 	isLandscape: -> !@isPortrait()

 	@define "orientationName",


Kind of quick fix for hitArea

Kind of quick fix for hitArea

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -36,6 +36,7 @@
 		options.width ?= 300
 		options.height ?= 10
 		options.value ?= 0
+		options.hitArea ?= options.height * 2 * 1.3

 		@knob = new Knob
 			backgroundColor: "#fff"
@@ -48,9 +49,9 @@
 			width: 0, force2d: true
 			name: "fill"

-		@knobOverlay = new Layer
-			backgroundColor: null
-			name: "knobOverlay"
+		# @knobOverlay = new Layer
+		# 	backgroundColor: null
+		# 	name: "knobOverlay"

 		@sliderOverlay = new Layer
 			backgroundColor: null
@@ -59,7 +60,7 @@
 		super options

 		@knobSize = options.knobSize or 30
-		@knob.parent = @fill.parent = @knobOverlay.parent = @sliderOverlay.parent = @
+		@knob.parent = @fill.parent = @sliderOverlay.parent = @

 		# Set fill initially
 		if @width > @height
@@ -68,11 +69,11 @@
 			@fill.width = @width

 		# Link knobOverlay to knob
-		@knobOverlay.on Events.Move, ->
-			if @width > @height
-				@knob.x = @x
-			else
-				@knob.y = @y
+		# @knobOverlay.on Events.Move, ->
+		# 	if @width > @height
+		# 		@knob.x = @x
+		# 	else
+		# 		@knob.y = @y

 		@fill.borderRadius = @sliderOverlay.borderRadius = @borderRadius

@@ -82,12 +83,6 @@
 		@knob.draggable.momentumOptions = {friction: 5, tolerance: 0.25}
 		@knob.draggable.bounce = false
 		@knob.borderRadius = @knobSize / 2
-		@knobOverlay.borderRadius = (@knob.borderRadius * 2) + (@hitArea / 4)
-
-		# If hitArea isn't defined, remove the overlays
-		if not @hitArea
-			@knobOverlay.destroy()
-			@sliderOverlay.destroy()

 		@_updateFrame()
 		@_updateKnob()
@@ -95,7 +90,6 @@

 		@on("change:size", @_updateFrame)
 		@on("change:borderRadius", @_setRadius)
-		@knob.on("change:borderRadius", @_setOverlayRadius)

 		# Check for vertical sliders
 		if @width > @height
@@ -111,12 +105,9 @@
 			@_updateFrame()
 			@_updateValue()

-		# On click/touch of the slider, update the value
-		# @on(Events.TouchStart, @_touchDown)
-
-		@on(Events.TapStart, @_touchStart)
-		@on(Events.Pan, @_touchMove)
-		@on(Events.TapEnd, @_touchEnd)
+		@sliderOverlay.on(Events.TapStart, @_touchStart)
+		@sliderOverlay.on(Events.Pan, @_touchMove)
+		@sliderOverlay.on(Events.TapEnd, @_touchEnd)

 	_touchStart: (event) =>
 		event.preventDefault()
@@ -133,28 +124,26 @@
 		@_updateValue()

 	_touchMove: (event) =>
-		if event.target is @_element
+		if event.target in [@_element, @sliderOverlay._element]
 			@knob.draggable._touchMove(event)

 	_touchEnd: (event) =>
-		if event.target is @_element
+		if event.target is [@_element, @sliderOverlay._element]
 			@knob.draggable._touchEnd(event)

 	_updateFill: =>
 		if @width > @height
-			@fill.width = @knobOverlay.midX = @knob.midX
-		else
-			@fill.height = @knobOverlay.midY = @knob.midY
+			@fill.width = @knob.midX
+		else
+			@fill.height = @knob.midY

 	_updateKnob: =>
 		if @width > @height
-			@knob.midX = @knobOverlay.midX = @fill.width
+			@knob.midX = @fill.width
 			@knob.centerY()
-
-		else
-			@knob.midY = @knobOverlay.midX = @fill.height
+		else
+			@knob.midY = @fill.height
 			@knob.centerX()
-			@knobOverlay.midY = @knob.midY

 	_updateFrame: =>
 		@knob.draggable.constraints =
@@ -173,20 +162,15 @@
 		if @width > @height
 			@fill.height = @height
 			@knob.centerY()
-			@knobOverlay.centerY()
 		else
 			@fill.width = @width
 			@knob.centerX()
-			@knobOverlay.centerX()

 		@sliderOverlay.center()

 	_setRadius: =>
 		radius = @borderRadius
 		@fill.style.borderRadius = "#{radius}px 0 0 #{radius}px"
-
-	_setOverlayRadius: =>
-		@knobOverlay.borderRadius = (@knob.borderRadius * 2) + (@hitArea / 4)

 	@define "knobSize",
 		get: -> @_knobSize
@@ -200,16 +184,9 @@
 		get: -> @_hitArea

 		set: (value) ->
-
 			@_hitArea = value
-
-			@knobOverlay.props =
-				width: @knobSize or 30 + @_hitArea
-				height:	@knobSize or 30 + @_hitArea
-
-			@sliderOverlay.props =
-				width: @width + @_hitArea
-				height:	@height + @_hitArea
+			@sliderOverlay.width = @width + @_hitArea
+			@sliderOverlay.height = @height + @_hitArea

 	@define "min",
 		get: -> @_min or 0


Mock out metrics in test

Mock out metrics in test

--- test/unit/coffee/ASpellTests.coffee
+++ test/unit/coffee/ASpellTests.coffee
@@ -10,6 +10,9 @@
 			"logger-sharelatex":
 				log:->
 				err:->
+			'metrics-sharelatex':
+				gauge:->
+				inc: ->

 	describe "a correctly spelled word", ->
 		beforeEach (done) ->


Fix autolinking

Fix autolinking

--- assets/javascripts/helpers/formatting.js.coffee
+++ assets/javascripts/helpers/formatting.js.coffee
@@ -36,7 +36,14 @@

     text = TentStatus.Helpers.htmlEscapeText(text)

+    offset = 0
     for i in TentStatus.Helpers.extractUrlsWithIndices(text)
-      text = TentStatus.Helpers.replaceIndexRange(i.indices[0], i.indices[1], text, "#{text.substring(i.indices[0], i.indices[1])}")
+      start_index = i.indices[0] + offset
+      end_index = i.indices[1] + offset
+      original_text = text.substring(start_index, end_index)
+      replace_text = "#{original_text}"
+      delta = replace_text.length - original_text.length
+      offset += delta
+      text = TentStatus.Helpers.replaceIndexRange(start_index, end_index, text, replace_text)

     text


fix typo

fix typo

--- plugins/links.coffee
+++ plugins/links.coffee
@@ -25,7 +25,7 @@
     title = results.title
     short = results.shorten
     if title || short
-      cb "#{if title then c.teal "\"#{title}\"" else ""}#{if short then c.underline short else ""}"
+      cb "#{if title then c.teal "\"#{title}\" " else ""}#{if short then c.underline short else ""}"
     else
       cb null


Set .htoprc file type to "text.generic-config" in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -50,6 +50,7 @@
     "^.gntrc": "source.ini"
     "^.gvimrc.after": "source.viml"
     "^.hgrc*": "source.ini"
+    "^.htoprc": "text.generic-config"
     "^.inputrc": "text.generic-config"
     "^.profile$": "source.shell"
     "^.psqlrc": "source.sql"


Fix typo in context menu (atom-minify.cson)

--- menus/atom-minify.cson
+++ menus/atom-minify.cson
@@ -30,6 +30,6 @@
         { 'label': 'Search in Directory', 'command': 'project-find:show-in-current-directory' }
         {'type': 'separator'}

-        {'label': 'Minifiy', 'command': 'atom-minify:minify-to-min-file', 'id': 'atom-minify-context-menu-minify' }
+        {'label': 'Minify', 'command': 'atom-minify:minify-to-min-file', 'id': 'atom-minify-context-menu-minify' }
         {'type': 'separator'}
     ]


validators: add a delegate self-check

validators: add a delegate self-check

this is a way of validating acceptable form of "ownership" when the method receiver is the delegate itself

--- workers/social/lib/social/models/group/validators.coffee
+++ workers/social/lib/social/models/group/validators.coffee
@@ -24,6 +24,9 @@

   own:(client, group, permission, permissionSet, callback)->
     {delegate} = client.connection
+
+    return callback null, yes  if delegate.equals this
+
     roleSelector = getRoleSelector delegate, group, permission, permissionSet
     # if we get -1 as the role selector, it means guest (i.e. anyone) is allowed
     return callback null, yes  if roleSelector is -1


ignore epubContainer in allContent, use 'singleton' one instead

--- scripts/gh-book/app.coffee
+++ scripts/gh-book/app.coffee
@@ -38,9 +38,7 @@
       when OpfFile::mediaType
         # add the opf to the copy of the Epubcontainer
         # that is in allContent
-        allContent
-          .findWhere({mediaType: EpubContainer::mediaType})
-          .addChild(model)
+        epubContainer.addChild(model)
       else
         allContent.each (book) ->
           book.manifest?.add(model) # Only books have a manifest
@@ -204,7 +202,8 @@
         changedModels = @filter (model) ->
           if contextModel && model != contextModel
             switch model.mediaType
-              when EpubContainer::mediaType then return model.isDirty() # Always add container file if it is dirty
+              # sometimes there is an epubContainer in allContent, this is bad - ignore it
+              when EpubContainer::mediaType then return false
               when OpfFile::mediaType then return model.isDirty() # Always add OPF files
               when XhtmlFile::mediaType
                 return includeNewContent and model.isNew()
@@ -215,6 +214,8 @@
       else
         # Save all the models that have changes
         changedModels = @filter (model) -> model.isDirty()
+
+      changedModels.push epubContainer if epubContainer.isDirty()

       writeFiles(changedModels)


WIP : why does search in serial break? Better logging.

--- tasks/lib/util.coffee
+++ tasks/lib/util.coffee
@@ -3,23 +3,21 @@
 exports.init = (grunt) ->
   shell = require("shelljs")
   lineReader = require("line-reader")
-
   exports = {}

   exports.db_dump = (config, output_paths) ->
+
     grunt.file.mkdir output_paths.dir

-    console.log config, 'config'
-
     if config.table_prefix
-      console.log "Using table prefix " + config.table_prefix
+      # grunt.log.oklns "Using table prefix \"" + config.table_prefix + "\""
       cmd = exports.prefixed_mysqldump_cmd(config)
     else
-      console.log "NOT using table prefix " + config.table_prefix
+      # grunt.log.oklns "NOT using table prefix \"" + config.table_prefix + "\""
       cmd = exports.mysqldump_cmd(config)

-    grunt.log.oklns "dump command:"
-    console.log cmd
+    # grunt.log.oklns "dump command:"
+    # console.log cmd

     output = shell.exec(cmd,
       silent: true
@@ -37,13 +35,14 @@
   exports.prefixed_mysqldump_cmd = (config, output_paths) ->

     prefix_matches = exports.all_table_prefix_matches(config)
-    grunt.log.oklns "Prefix matches from '" + config.table_prefix + "'"
-    console.log prefix_matches
+    grunt.log.oklns "Prefix matches from search '" + config.table_prefix + "'"
+    console.log grunt.log.wordlist( prefix_matches )

     tables_to_dump = exports.tables_to_dump(config, prefix_matches)
     grunt.log.oklns "Tables to dump"

-    console.log tables_to_dump
+    # grunt.log.verbose 'verbosion test'
+    console.log grunt.log.wordlist( tables_to_dump )

     # Return the cmd
     exports.build_prefixed_sqldump(config, tables_to_dump)
@@ -80,11 +79,12 @@
         sql_connect: sql_connect
         tables_sql: tables_sql
     )
-
-    if typeof config.ssh_host is "undefined"
-      grunt.log.oklns "Getting prefix matches in LOCAL db/prefix ['" + config.database + "'/'" + config.table_prefix + "']"
-    else
-      grunt.log.oklns "Getting prefix matches in REMOTE db/prefix ['" + config.database + "'/'" + config.table_prefix + "']"
+
+    dest = if config.ssh_host? then 'remote' else 'local'
+
+    grunt.log.ok "Getting prefix matches from " + dest + " db:'" + config.database + "' prefix:'" + config.table_prefix + "'"
+
+    if config.ssh_host?
       tpl_ssh = grunt.template.process(tpls.ssh,
         data:
           host: config.ssh_host
@@ -181,7 +181,7 @@

     dest = if config.ssh_host? then 'remote' else 'local'

-    grunt.log.oklns "Creating prefixed DUMP of " + dest + " database [" + config.database + "/" + config.table_prefix + "]"
+    grunt.log.ok "Creating DUMP of " + dest + " database '" + config.database + "' with prefix '" + config.table_prefix + "'"

     # console.log "prefixed sqldump cmd\n" + cmd
     cmd
@@ -245,21 +245,49 @@
     exclusions = exclusions.trim()
     exclusions

-  exports.db_adapt = (old_url, new_url, file) ->
-    grunt.log.oklns "Adapt the database: set the correct urls for the destination in the database."
+  exports.db_adapt = (search_options, replace_options, file) ->
+    grunt.log.oklns "Adapt the database:"
+    grunt.writeln
+
+    old_url = search_options.url
+    new_url = replace_options.url
+
     content = grunt.file.read(file)
+    grunt.log.oklns "set the correct urls for the destination in the database..."
+    console.log { old_url, new_url }
+    grunt.writeln
+
     output = exports.replace_urls(old_url, new_url, content)
+
+    old_prefix = search_options.table_prefix
+    new_prefix = replace_options.table_prefix
+    grunt.log.oklns "New/Old prefixes"
+    grunt.writeln
+    console.log { old_prefix, new_prefix }
+
+    if old_prefix && new_prefix
+      grunt.log.ok "Swap out old table prefix for new table prefix [ old: " + old_prefix + " | new: " + new_prefix + " ]..."
+      # output = exports.replace_table_prefix( old_prefix, new_prefix, output )
+
+    output = "-- Database Adapted via grunt-wordpress-deploy on " + grunt.template.today("yyyy-mm-dd at HH-MM-ss") + "\n" + output
+
     grunt.file.write file, output
     return

   exports.replace_urls = (search, replace, content) ->
+    console.log 'replacing urls in serialized'
     content = exports.replace_urls_in_serialized(search, replace, content)
+    console.log 'replacing urls in string'
     content = exports.replace_urls_in_string(search, replace, content)
     content

+  exports.replace_table_prefix = (old_prefix, new_prefix, sqldump_output) ->
+    regexp = new RegExp("(?!" + new_prefix + ")(" + old_prefix + ")", "g")
+    sqldump_output.replace regexp, new_prefix
+
   exports.replace_urls_in_serialized = (search, replace, string) ->
     length_delta = search.length - replace.length
-
+
     # Replace for serialized data
     matches = undefined
     length = undefined
@@ -277,7 +305,7 @@
       if target_string.indexOf(search) isnt -1
         length = matches[1]
         delimiter = matches[2]
-
+
         # Replace the url
         new_url = target_string.replace(search, replace)
         length -= length_delta
@@ -304,9 +332,9 @@
         host: config.host
     )
     if typeof config.ssh_host is "undefined"
-      grunt.log.oklns "Building dump cmd for LOCAL database [" + config.database + "]"
+      # grunt.log.oklns "Building dump cmd for LOCAL database [" + config.database + "]"
     else
-      grunt.log.oklns "Building dump cmd for REMOTE database [" + config.database + "]"
+      # grunt.log.oklns "Building dump cmd for REMOTE database [" + config.database + "]"
       tpl_ssh = grunt.template.process(tpls.ssh,
         data:
           host: config.ssh_host


Add more symbols to the font

Add more symbols to the font


Bugfix

Bugfix

--- cs_app/sabnzbd.coffee
+++ cs_app/sabnzbd.coffee
@@ -42,13 +42,13 @@
     funcs = [
       (cb) ->
         SABnzbd.getSabData 'queue', (d) ->
-          if d != ''
+          if _.isString(d) and d isnt ''
             cb null, JSON.parse(d).queue
           else
             cb 'err', '',
       (cb) ->
         SABnzbd.getSabData 'history', (d) ->
-          if d != ''
+          if _.isString(d) and d isnt ''
             cb null, JSON.parse(d).history
           else
             cb 'err', ''


Test spec for array arguments

Test spec for array arguments

--- test/spec/invoke.spec.coffee
+++ test/spec/invoke.spec.coffee
@@ -92,3 +92,16 @@
             expect(->
                 overloadableFunction 7, "foo"
             ).toThrow()
+
+    describe "Array arguments", ->
+        beforeEach ->
+            overloadableFunction = new Overloadable
+            spiedFunction.reset()
+
+        it "Should recognize array arguments and use them as an alternative", ->
+            overloadableFunction.overload [["number", "string"]], spiedFunction
+
+            overloadableFunction 1
+            overloadableFunction "a"
+
+            expect(spiedFunction.callCount).toBe 2


Remove grunt source file modification watch hack

--- bokehjs/Gruntfile.coffee
+++ bokehjs/Gruntfile.coffee
@@ -176,16 +176,3 @@
   grunt.registerTask("build",      ["coffee", "less", "copy", "eco"])
   grunt.registerTask("deploy",     ["build",  "requirejs:dist", "clean"])
   grunt.registerTask("devdeploy",  ["build",  "requirejs:dev",  "clean"])
-
-
-  grunt.event.on "watch", (action, filepath, target) ->
-    filepath = filepath.replace('src/coffee/', '')
-    grunt.config.set('coffee',
-      changed:
-        expand: true
-        cwd: 'src/coffee'
-        src: filepath
-        dest: 'build/js'
-        ext: '.js'
-    )
-    grunt.task.run('coffee:changed')


move draw_ellipse to Shape

move draw_ellipse to Shape

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -9,6 +9,24 @@
     @context.beginPath()
     @context.arc x, y, width, 0, Math.PI*2, true
     @context.closePath()
+    @context.fill()
+
+  draw_ellipse:(x, y, w, h) ->
+    kappa = .5522848;
+    ox = (w / 2) * kappa
+    oy = (h / 2) * kappa
+    xe = x + w
+    ye = y + h
+    xm = x + w / 2
+    ym = y + h / 2
+
+    @context.beginPath();
+    @context.moveTo(x, ym);
+    @context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
+    @context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
+    @context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
+    @context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
+    @context.closePath();
     @context.fill()

 class Building
@@ -87,25 +105,9 @@
   draw_colission:(x, y) ->
     color = '#0000a0'
     @context.fillStyle = color
-    @draw_ellipse(x, y, 35, 25)
-
-  draw_ellipse:(x, y, w, h) ->
-    kappa = .5522848;
-    ox = (w / 2) * kappa
-    oy = (h / 2) * kappa
-    xe = x + w
-    ye = y + h
-    xm = x + w / 2
-    ym = y + h / 2
-
-    @context.beginPath();
-    @context.moveTo(x, ym);
-    @context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
-    @context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
-    @context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
-    @context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
-    @context.closePath();
-    @context.fill()
+    shape = new Shape(@context)
+    shape.draw_ellipse(x, y, 35, 25)
+
 class Sun
   constructor:(context) ->
     @mouth = true
@@ -383,7 +385,8 @@

   draw_as_dead: ->
     @context.fillStyle = '#0000a0'
-    @draw_ellipse(@x-@width, @y, 2.5*@explosion_width, @explosion_height)
+    shape = new Shape(@context)
+    shape.draw_ellipse(@x-@width, @y, 2.5*@explosion_width, @explosion_height)

   animate_colission:() ->
     @context.fillStyle = '#F50B0B'
@@ -392,27 +395,8 @@
     width = @explosion_width
     height = @explosion_height

-    @draw_ellipse(@x-@width, @y, 2.5*width, height)
-
-
-  draw_ellipse:(x, y, w, h) ->
-    kappa = .5522848;
-    ox = (w / 2) * kappa
-    oy = (h / 2) * kappa
-    xe = x + w
-    ye = y + h
-    xm = x + w / 2
-    ym = y + h / 2
-
-    @context.beginPath();
-    @context.moveTo(x, ym);
-    @context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
-    @context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
-    @context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
-    @context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
-    @context.closePath();
-    @context.fill()
-
+    shape = new Shape(@context)
+    shape.draw_ellipse(@x-@width, @y, 2.5*width, height)

   throw_banana:(time, just_thrown) ->
     if @player_number == 2 and just_thrown == true


Changed: Atom tree view now show ignored/vcs ignored files

--- dotfiles/atom/config.cson
+++ dotfiles/atom/config.cson
@@ -152,8 +152,6 @@
     ignoreWhitespaceOnCurrentLine: false
   "tree-view":
     showOnRightSide: true
-    hideIgnoredNames: true
-    hideVcsIgnoredFiles: true
   "find-and-replace":
     openProjectFindResultsInRightPane: true
     focusEditorAfterSearch: true


remove callback after closing.

remove callback after closing.

--- src/code/models/image-manager.coffee
+++ src/code/models/image-manager.coffee
@@ -24,6 +24,7 @@
   onOpen: (callback=false)->
     @keepShowing = true
     @lastImage = null
+    @callback = null
     if callback
       @callback = callback
       @keepShowing = false


revert to previous one liner

revert to previous one liner


FIX: HasProperties set should not modify inputs

--- bokehjs/src/coffee/common/has_properties.coffee
+++ bokehjs/src/coffee/common/has_properties.coffee
@@ -33,7 +33,7 @@
       super(attrs, options)

       #cheap memoization, requirejs doesn't seem to do it
-      @_base = false
+      @_base = false
       @properties = {}
       @property_cache = {}
       if not _.has(attrs, @idAttribute)
@@ -82,8 +82,10 @@
            @properties[key]['setter']
           @properties[key]['setter'].call(this, val)
           toremove.push(key)
-      for key in toremove
-        delete attrs[key]
+      if not _.isEmpty(toremove)
+        attrs = _.clone(attrs)
+        for key in toremove
+          delete attrs[key]
       if not _.isEmpty(attrs)
         super(attrs, options)

@@ -242,7 +244,7 @@
       if not @_base
         @_base = require('./base')
       return @_base
-
+
     url: () ->
       # ### method HasProperties::url
       #model where our API processes this model


higher performance circle rendering when reading from a column data source

--- bokehjs/src/coffee/renderer/glyph/circle.coffee
+++ bokehjs/src/coffee/renderer/glyph/circle.coffee
@@ -36,14 +36,75 @@
       return glyph_props

     _set_data: (@data) ->
-      @x = @glyph_props.v_select('x', data)
-      @y = @glyph_props.v_select('y', data)
+      @x = @glyph_props.data_v_select('x', data)
+      @y = @glyph_props.data_v_select('y', data)
       @mask = new Uint8Array(data.length)
       @selected_mask = new Uint8Array(data.length)
       for i in [0..@mask.length-1]
         @mask[i] = true
         @selected_mask[i] = false
       @have_new_data = true
+
+    set_data: (request_render=true) ->
+      source = @mget_obj('data_source')
+      if source.type == 'ColumnDataSource'
+        @x = @source_v_select('x', @glpyhprops, source)
+        @y = @source_v_select('y', @glyphprops, source)
+        @mask = new Uint8Array(data.length)
+        @selected_mask = new Uint8Array(data.length)
+        for i in [0..@mask.length-1]
+          @mask[i] = true
+          @selected_mask[i] = false
+        @have_new_data = true
+
+      if request_render
+        @request_render()
+
+
+    source_v_select: (attrname, glyphprops, datasource) ->
+      # if the attribute is not on this property object at all, log a bad request
+      if not (attrname of glpyhprops)
+        console.log("requested vector selection of unknown property '#{ attrname }' on objects")
+        return
+
+      prop = glyphprops[attrname]
+      # if prop.typed?
+      #   result = new Float64Array(objs.length)
+
+      # if the attribute specifies a field, and the field exists on
+      # the column source, return the column from the column source
+
+
+      if prop.field? and (prop.field of datasource.get('data'))
+        console.log("source_v_select")
+        return source.getcolumn(prop.field)
+      else
+        result = new Array(objs.length)
+      '''
+      objs = []
+      for i in [0..objs.length-1]
+        obj = objs[i]
+
+
+        # If the user gave an explicit value, that should always be returned
+        else if glyphprops[attrname].value?
+          result[i] = glyphprops[attrname].value
+
+        # otherwise, if the attribute exists on the object, return that value
+        else if obj[attrname]?
+          result[i] = obj[attrname]
+
+        # finally, check for a default value on this property object that could be returned
+        else if glyphprops[attrname].default?
+          result[i] = glyphprops[attrname].default
+
+        # failing that, just log a problem
+        else
+          console.log "vector selection for attribute '#{ attrname }' failed on object: #{ obj }"
+          return
+
+      return result
+      '''

     _render: (plot_view, have_new_mapper_state=true) ->
       [@sx, @sy] = @plot_view.map_to_screen(@x, @glyph_props.x.units, @y, @glyph_props.y.units)


changing from console to $log

changing from console to $log

--- src/app/components/directive.coffee
+++ src/app/components/directive.coffee
@@ -9,7 +9,7 @@
       ngFillHeightOption.maxCallNumber or= 10000

       if typeof ngFillHeightOption isnt 'object'
-        console.error 'The value of ngFillHeight has to be an Object'
+        $log.error 'The value of ngFillHeight has to be an Object'
         return

       parentObject = angular.element(ngFillHeightOption.parentSelector)


More system functions for javac.

More system functions for javac.

--- src/methods.coffee
+++ src/methods.coffee
@@ -319,6 +319,12 @@
             exp = gLong.fromNumber(Math.floor(Math.log(d_val)/Math.LN2)+1023)
             sig = gLong.fromNumber((d_val/Math.pow(2,exp)-1)/Math.pow(2,-52))
             sign.shiftLeft(63).add(exp.shiftLeft(52)).add(sig)
+        o 'longBitsToDouble(J)D', (rs, l_val) ->
+            s = if l_val.shiftRight(63).equals(gLong.fromInt(0)) then 1 else -1
+            e = l_val.shiftRight(52).and(gLong.fromInt(0x7ff))
+            m = if e == 0 then l_val.and(gLong.fromNumber(0xfffffffffffff))
+                                    .or(gLong.fromNumber(0x10000000000000))
+            Math.pow(2, e * 1075) * s * m # we're not handling the NaN / Inf cases
       ]
       Object: [
         o 'getClass()L!/!/Class;', (rs, _this) ->
@@ -482,6 +488,17 @@
       ]
       ObjectStreamClass: [
         o 'initNative()V', (rs) ->  # NOP
+      ]
+      RandomAccessFile: [
+        o 'open(Ljava/lang/String;I)V', (rs, _this, name, mode) ->
+            mode_str = 'r'
+            mode_str += 'w' if mode & 2 # there's also the sync flag but we're ignoring that
+            # TODO make this an object so we can write back
+            fname = rs.jvm2js_str(name)
+            error "Attempting to use #{fname} as a RandomAccessFile -- may not be correct!"
+            _this.fields.$file = fs.readFileSync fname
+        o 'length()J', (rs, _this) -> _this.fields.$file.length
+        o 'close0()V', (rs) ->
       ]
       UnixFileSystem: [
         o 'getBooleanAttributes0(Ljava/io/File;)I', (rs, _this, file) ->


Clean up slopy JSONStream usage.

Clean up slopy JSONStream usage.

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -328,20 +328,17 @@
     res.render('static.html', info)

   app.get ///system/factories.json///, (req, res) ->
+    res.status(200)
+    res.header('Content-Type', 'application/json')
     glob path.join(argv.c, '**', 'factory.json'), (e, files) ->
       if e then return res.e(e)
       files = files.map (file) ->
-        return fs.createReadStream(file).on('error', res.e).pipe(
-          JSONStream.parse([false]).on 'root', (el) ->
-            @emit 'data', el
-        )
+        return fs.createReadStream(file).on('error', res.e).pipe(JSONStream.parse())

       es.concat.apply(null, files)
         .on('error', res.e)
-        .pipe(es.writeArray((e, body) ->
-          if e then return res.e(e)
-          res.json(body)
-        ))
+        .pipe(JSONStream.stringify())
+        .pipe(res)

   ###### Json Routes ######


Add test for normalizing route path

Add test for normalizing route path

--- test/api_tests.coffee
+++ test/api_tests.coffee
@@ -147,6 +147,17 @@

   describe 'An API endpoint', ->
+
+    it 'should normalize path starting with a forward slash', (test) ->
+      Api.addRoute '/forward-slash',
+        get: ->
+          true
+
+      result = HTTP.get Meteor.absoluteUrl 'api/forward-slash'
+
+      test.equal result.statusCode, 200
+      test.equal result.headers['content-type'], 'text/json'
+      test.isTrue result.content

     it 'should respond with the default headers when not overridden', (test) ->
       Api.addRoute 'default-headers',


status: honors conditions

status: honors conditions

--- test/promise.status.coffee
+++ test/promise.status.coffee
@@ -3,9 +3,9 @@
 test = require './test'
 fs = require 'fs'

-describe 'promise status', ->
+describe 'status', ->

-  it 'get status', (next) ->
+  it 'get without arguments', (next) ->
     mecano
     .call (options, callback) ->
       @status().should.be.false()
@@ -23,7 +23,7 @@
       callback null, false
     .then next

-  it 'get current status', (next) ->
+  it 'get current', (next) ->
     mecano
     .call (options, callback) ->
       (@status(0) is undefined).should.be.true()
@@ -37,7 +37,7 @@
       @status(0).should.be.true()
     .then next

-  it 'get previous status', (next) ->
+  it 'get previous', (next) ->
     mecano
     .call (options, callback) ->
       (@status(-1) is undefined).should.be.true()
@@ -78,3 +78,25 @@
       @status(-2).should.be.false()
       callback null, false
     .then next
+
+  it 'honors conditions', (next) ->
+    mecano
+    .call
+      if: -> true
+    ,(options, callback) ->
+      callback null, true
+    .then (err, status) ->
+      return next err if err
+      status.should.be.true()
+    .call
+      if: -> false
+    ,(options, callback) ->
+      callback null, true
+    .then (err, status) ->
+      return next err if err
+      status.should.be.false()
+      next()
+
+
+
+


Cloning the object before removing properties

--- src/components/RemoveProperty.coffee
+++ src/components/RemoveProperty.coffee
@@ -22,6 +22,9 @@
             @outPorts.out.disconnect()

     removeProperties: (object) ->
+        # Clone the object so that the original isn't changed, the lib-less way
+        object = JSON.parse(JSON.stringify(object))
+
         for property in @properties
             delete object[property]
         return object


Fixes #1360

Fixes #1360

--- server/lib/accounts.coffee
+++ server/lib/accounts.coffee
@@ -1,8 +1,8 @@
 # Deny Account.createUser in client and set Meteor.loginTokenExpires
 accountsConfig = { forbidClientAccountCreation: true, loginExpirationInDays: RocketChat.settings.get 'Accounts_LoginExpiration' }

-if RocketChat.settings.get('Account_AllowedDomainsList')
-	domainWhiteList = _.map RocketChat.settings.get('Account_AllowedDomainsList').split(','), (domain) -> domain.trim()
+if RocketChat.settings.get('Accounts_AllowedDomainsList')
+	domainWhiteList = _.map RocketChat.settings.get('Accounts_AllowedDomainsList').split(','), (domain) -> domain.trim()
 	accountsConfig.restrictCreationByEmailDomain = (email) ->
 		ret = false
 		for domain in domainWhiteList


Type check articleId in publication

Type check articleId in publication

--- server/publications.coffee
+++ server/publications.coffee
@@ -41,6 +41,7 @@
   SmartEvents.find({deleted: {$in: [null, false]}})

 Meteor.publish 'ArticleIncidentReports', (articleId) ->
+  check(articleId, Match.Maybe(String))
   Incidents.find articleId: articleId,
     sort: 'annotations.case.0.textOffsets.0': 1


Fix bug

Fix bug

--- server/publications.coffee
+++ server/publications.coffee
@@ -1,6 +1,6 @@
 Meteor.publish "needs", (query) ->
-  query = query.toLowerCase()
   if query
+    query = query.toLowerCase()
     Needs.find {completedAt: {$exists: false},  $or: [ { content: {$regex: "/^#{query}$/", $options: 'i'} }, { tags: { $in: [ query ] } } ] }, {sort: {score: -1}}
   else
     Needs.find {completedAt: {$exists: false}}, {sort: {score: -1}}


Better error logging from admin page

Better error logging from admin page

--- server/routes/admin.coffee
+++ server/routes/admin.coffee
@@ -10,6 +10,7 @@
   ).then( (indicatorData) ->
     return res.send(201, "Successfully updated indicator:\n #{JSON.stringify indicatorData}")
   ).fail((err) ->
+    console.log err
     console.log err.stack
     return res.send(500, "Error updating the indicator")
   )


move cache handler up in the list of plugins

--- src/octokat.coffee
+++ src/octokat.coffee
@@ -20,9 +20,10 @@
 ALL_PLUGINS = MIDDLEWARE_REQUEST_PLUGINS.concat([
   MIDDLEWARE_RESPONSE_PLUGINS.READ_BINARY
   MIDDLEWARE_RESPONSE_PLUGINS.PAGED_RESULTS
+  MIDDLEWARE_CACHE_HANDLER  # Run cacheHandler after PagedResults so the link headers are remembered
+                            # but before hypermedia so the object is still serializable
   MIDDLEWARE_RESPONSE_PLUGINS.HYPERMEDIA
   MIDDLEWARE_RESPONSE_PLUGINS.CAMEL_CASE
-  MIDDLEWARE_CACHE_HANDLER
 ])


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -54,7 +54,7 @@
     width: 10
     height: 10

-  start =
+  start = Point(0, 0)
   Collision.rayRectangle
 module()


added latest tests

added latest tests

--- test/crud.test.coffee
+++ test/crud.test.coffee
@@ -106,22 +106,22 @@
     PostWithNumberUnderscoreId.create {_id: 3, content: "test"}, (err, person) ->
       should.not.exist(err)
       person._id.should.be.equal(3)
-    PostWithNumberUnderscoreId.findById person._id, (err, p) ->
-      should.not.exist(err);
-      p.content.should.be.equal('test');
-
-      done()
+      PostWithNumberUnderscoreId.findById person._id, (err, p) ->
+        should.not.exist(err);
+        p.content.should.be.equal('test');
+
+        done()

   it 'should handle correctly type Number for id field _id using string', (done) ->

     PostWithNumberUnderscoreId.create {_id: 4, content: 'test'}, (err, person) ->
       should.not.exist(err);
       person._id.should.be.equal(4);
-    PostWithNumberUnderscoreId.findById '4', (err, p) ->
-      should.not.exist(err);
-      p.content.should.be.equal('test');
-
-      done()
+      PostWithNumberUnderscoreId.findById '4', (err, p) ->
+        should.not.exist(err);
+        p.content.should.be.equal('test');
+
+        done()

 #  it 'should allow to find post by id string if `_id` is defined id', (done) ->
 #
@@ -179,19 +179,19 @@
 #
 #        done()

-  it 'all return should honor filter.fields, with `_id` as defined id', (done) ->
-
-    post = new PostWithObjectId {title: 'a', content: 'AAA'}
-    post.save (err, post) ->
-      PostWithObjectId.all {fields: ['title'], where: {title: 'a'}}, (err, posts) ->
-        should.not.exist(err)
-        posts.should.have.lengthOf(1)
-        post = posts[0]
-        post.should.have.property('title', 'a')
-        post.should.have.property('content', undefined)
-        should.not.exist(post._id)
-
-        done()
+#  it 'all return should honor filter.fields, with `_id` as defined id', (done) ->
+#
+#    post = new PostWithObjectId {title: 'a', content: 'AAA'}
+#    post.save (err, post) ->
+#      PostWithObjectId.all {fields: ['title'], where: {title: 'a'}}, (err, posts) ->
+#        should.not.exist(err)
+#        posts.should.have.lengthOf(1)
+#        post = posts[0]
+#        post.should.have.property('title', 'a')
+#        post.should.have.property('content', undefined)
+#        should.not.exist(post._id)
+#
+#        done()

   it 'should have created simple User models', (done) ->

@@ -301,6 +301,7 @@
         done(err, results)

   it 'should allow to find by number id using where', (done) ->
+
     PostWithNumberId.create {id: 1, title: 'Post1', content: 'Post1 content'}, (err, p1) ->
       PostWithNumberId.create {id: 2, title: 'Post2', content: 'Post2 content'}, (err, p2) ->
         PostWithNumberId.find {where: {id: p1.id}}, (err, p) ->
@@ -312,6 +313,7 @@
           done()

   it 'should allow to find by number id using where inq', (done) ->
+
     PostWithNumberId.create {id: 1, title: 'Post1', content: 'Post1 content'}, (err, p1) ->
       PostWithNumberId.create {id: 2, title: 'Post2', content: 'Post2 content'}, (err, p2) ->
         PostWithNumberId.find {where: {id: {inq: [1]}}}, (err, p) ->
@@ -349,3 +351,468 @@
         should.not.exist(post._key)

         done()
+
+  describe 'updateAll', () ->
+
+    it 'should update the instance matching criteria', (done) ->
+
+      User.create {name: 'Al', age: 31, email:'al@strongloop'}, (err1, createdusers1) ->
+        should.not.exist(err1);
+      User.create {name: 'Simon', age: 32,  email:'simon@strongloop'}, (err2, createdusers2) ->
+        should.not.exist(err2);
+      User.create {name: 'Ray', age: 31,  email:'ray@strongloop'}, (err3, createdusers3) ->
+        should.not.exist(err3);
+
+        User.updateAll {age:31},{company:'strongloop.com'}, (err, updatedusers) ->
+          should.not.exist(err);
+          updatedusers.should.have.property('count', 2);
+
+        User.find {where:{age:31}}, (err2, foundusers) ->
+          should.not.exist(err2);
+          foundusers[0].company.should.be.equal('strongloop.com');
+          foundusers[1].company.should.be.equal('strongloop.com');
+
+          done()
+
+  it 'updateOrCreate should update the instance', (done) ->
+
+    Post.create {title: 'a', content: 'AAA'}, (err, post) ->
+      post.title = 'b';
+      Post.updateOrCreate post, (err, p) ->
+        should.not.exist(err);
+        p.id.should.be.equal(post.id);
+        p.content.should.be.equal(post.content);
+        should.not.exist(p._key);
+
+      Post.findById post.id, (err, p) ->
+        p.id.should.be.eql(post.id);
+        should.not.exist(p._key);
+        p.content.should.be.equal(post.content);
+        p.title.should.be.equal('b');
+
+        done()
+
+  it 'updateOrCreate should update the instance without removing existing properties', (done) ->
+
+    Post.create {title: 'a', content: 'AAA', comments: ['Comment1']}, (err, post) ->
+      post = post.toObject()
+      delete post.title
+      delete post.comments;
+      Post.updateOrCreate post, (err, p) ->
+        should.not.exist(err)
+        p.id.should.be.equal(post.id)
+        p.content.should.be.equal(post.content)
+        should.not.exist(p._key)
+
+        Post.findById post.id, (err, p) ->
+          p.id.should.be.eql(post.id)
+          should.not.exist(p._key)
+          p.content.should.be.equal(post.content)
+          p.title.should.be.equal('a')
+          p.comments[0].should.be.equal('Comment1')
+
+          done()
+
+  it 'updateOrCreate should create a new instance if it does not exist', (done) ->
+
+    post = {id: '123', title: 'a', content: 'AAA'};
+    Post.updateOrCreate post, (err, p) ->
+      should.not.exist(err)
+      p.title.should.be.equal(post.title)
+      p.content.should.be.equal(post.content)
+      p.id.should.be.eql(post.id)
+
+      Post.findById p.id, (err, p) ->
+        p.id.should.be.equal(post.id)
+        should.not.exist(p._id)
+        p.content.should.be.equal(post.content)
+        p.title.should.be.equal(post.title)
+        p.id.should.be.equal(post.id)
+
+        done()
+
+  it 'save should update the instance with the same id', (done) ->
+
+    Post.create {title: 'a', content: 'AAA'}, (err, post) ->
+      post.title = 'b';
+      post.save (err, p) ->
+      should.not.exist(err)
+      p.id.should.be.equal(post.id)
+      p.content.should.be.equal(post.content)
+      should.not.exist(p._key)
+
+      Post.findById post.id, (err, p) ->
+        p.id.should.be.eql(post.id)
+        should.not.exist(p._key)
+        p.content.should.be.equal(post.content)
+        p.title.should.be.equal('b')
+
+        done()
+
+  it 'save should update the instance without removing existing properties', (done) ->
+
+    Post.create {title: 'a', content: 'AAA'}, (err, post) ->
+      delete post.title
+      post.save (err, p) ->
+        should.not.exist(err);
+        p.id.should.be.equal(post.id);
+        p.content.should.be.equal(post.content);
+        should.not.exist(p._key);
+
+        Post.findById post.id, (err, p) ->
+          p.id.should.be.eql(post.id)
+          should.not.exist(p._key)
+          p.content.should.be.equal(post.content)
+          p.title.should.be.equal('a')
+
+          done()
+
+  it 'save should create a new instance if it does not exist', (done) ->
+
+    post = new Post {id: '123', title: 'a', content: 'AAA'}
+    post.save post, (err, p) ->
+      should.not.exist(err);
+      p.title.should.be.equal(post.title);
+      p.content.should.be.equal(post.content);
+      p.id.should.be.equal(post.id);
+
+      Post.findById p.id, (err, p) ->
+        p.id.should.be.equal(post.id);
+        should.not.exist(p._key);
+        p.content.should.be.equal(post.content);
+        p.title.should.be.equal(post.title);
+        p.id.should.be.equal(post.id);
+
+        done()
+
+  it 'all should return object with an id, which is instanceof String, but not mongodb _key', (done) ->
+    post = new Post {title: 'a', content: 'AAA'}
+    post.save (err, post) ->
+      Post.all {where: {title: 'a'}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.lengthOf(1)
+        post = posts[0]
+        post.should.have.property('title', 'a')
+        post.should.have.property('content', 'AAA')
+        post.id.should.be.an.instanceOf(String)
+        should.not.exist(post._key)
+
+        done()
+
+  it 'all return should honor filter.fields', (done) ->
+    post = new Post {title: 'b', content: 'BBB'}
+    post.save (err, post) ->
+      Post.all {fields: ['title'], where: {title: 'b'}}, (err, posts) ->
+        should.not.exist(err);
+        posts.should.have.lengthOf(1);
+        post = posts[0];
+        post.should.have.property('title', 'b');
+        post.should.have.property('content', undefined);
+        should.not.exist(post._key);
+        should.not.exist(post.id);
+
+        done()
+
+  it 'find should order by id if the order is not set for the query filter', (done) ->
+    PostWithStringId.create {id: '2', title: 'c', content: 'CCC'}, (err, post) ->
+      PostWithStringId.create {id: '1', title: 'd', content: 'DDD'}, (err, post) ->
+        PostWithStringId.find (err, posts) ->
+          should.not.exist(err)
+          posts.length.should.be.equal(2)
+          posts[0].id.should.be.equal('1')
+
+    PostWithStringId.find {limit: 1, offset: 0}, (err, posts) ->
+      should.not.exist(err);
+      posts.length.should.be.equal(1)
+      posts[0].id.should.be.equal('1')
+
+    PostWithStringId.find {limit: 1, offset: 1}, (err, posts) ->
+      should.not.exist(err);
+      posts.length.should.be.equal(1)
+      posts[0].id.should.be.equal('2')
+
+      done()
+
+  it 'should report error on duplicate keys', (done) ->
+    Post.create {title: 'd', content: 'DDD'}, (err, post) ->
+      Post.create {id: post.id, title: 'd', content: 'DDD'}, (err, post) ->
+        should.exist(err)
+
+        done()
+
+  it 'should allow to find using like', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {like: 'M.+st'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should allow to find using case insensitive like', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {like: 'm.+st', options: 'i'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should allow to find using case insensitive like', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {content: {like: 'HELLO', options: 'i'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should support like for no match', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {like: 'M.+XY'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  it 'should allow to find using nlike', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {nlike: 'M.+st'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  it 'should allow to find using case insensitive nlike', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {nlike: 'm.+st', options: 'i'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  it 'should support nlike for no match', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {nlike: 'M.+XY'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should support "and" operator that is satisfied', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {and: [{title: 'My Post'}, {content: 'Hello'}]}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should support "and" operator that is not satisfied', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {and: [{title: 'My Post'}, {content: 'Hello1'}]}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  it 'should support "or" that is satisfied', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {or: [{title: 'My Post'}, {content: 'Hello1'}]}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+        done()
+
+  it 'should support "or" operator that is not satisfied', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {or: [{title: 'My Post1'}, {content: 'Hello1'}]}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  it 'should support "nor" operator that is satisfied', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {nor: [{title: 'My Post1'}, {content: 'Hello1'}]}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should support "nor" operator that is not satisfied', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {nor: [{title: 'My Post'}, {content: 'Hello1'}]}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  it 'should support neq for match', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {neq: 'XY'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 1)
+
+        done()
+
+  it 'should support neq for no match', (done) ->
+
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.find {where: {title: {neq: 'My Post'}}}, (err, posts) ->
+        should.not.exist(err)
+        posts.should.have.property('length', 0)
+
+        done()
+
+  # The where object should be parsed by the connector
+  it 'should support where for count', (done) ->
+    Post.create {title: 'My Post', content: 'Hello'}, (err, post) ->
+      Post.count {and: [{title: 'My Post'}, {content: 'Hello'}]}, (err, count) ->
+        should.not.exist(err)
+        count.should.be.equal(1)
+        Post.count {and: [{title: 'My Post1'}, {content: 'Hello'}]}, (err, count) ->
+          should.not.exist(err)
+          count.should.be.equal(0)
+          done()
+
+  # The where object should be parsed by the connector
+  it 'should support where for destroyAll', (done) ->
+
+    Post.create {title: 'My Post1', content: 'Hello'}, (err, post) ->
+      Post.create {title: 'My Post2', content: 'Hello'}, (err, post) ->
+        Post.destroyAll {and: [
+            {title: 'My Post1'},
+            {content: 'Hello'}
+          ]}, (err) ->
+          should.not.exist(err)
+          Post.count (err, count) ->
+            should.not.exist(err)
+            count.should.be.equal(1)
+            done()
+
+  context 'regexp operator', () ->
+    before () ->
+      deleteExistingTestFixtures (done) ->
+        Post.destroyAll(done)
+
+    beforeEach () ->
+      createTestFixtures (done) ->
+        Post.create [
+          {title: 'a', content: 'AAA'},
+          {title: 'b', content: 'BBB'}
+        ], done
+
+    after () ->
+      deleteTestFixtures (done) ->
+        Post.destroyAll(done);
+
+    context 'with regex strings', () ->
+      context 'using no flags', () ->
+        it 'should work', (done) ->
+          Post.find {where: {content: {regexp: '^A'}}}, (err, posts) ->
+            should.not.exist(err)
+            posts.length.should.equal(1)
+            posts[0].content.should.equal('AAA')
+            done()
+
+      context 'using flags', () ->
+        beforeEach () ->
+          addSpy () ->
+            sinon.stub(console, 'warn');
+
+        afterEach () ->
+          removeSpy ->
+            console.warn.restore();
+
+        it 'should work', (done) ->
+          Post.find {where: {content: {regexp: '^a/i'}}}, (err, posts) ->
+            should.not.exist(err)
+            posts.length.should.equal(1)
+            posts[0].content.should.equal('AAA')
+            done()
+
+        it 'should print a warning when the global flag is set', (done) ->
+            Post.find {where: {content: {regexp: '^a/g'}}}, (err, posts) ->
+              console.warn.calledOnce.should.be.ok
+              done()
+
+    context 'with regex literals', () ->
+      context 'using no flags', () ->
+        it 'should work', (done) ->
+          Post.find {where: {content: {regexp: /^A/}}}, (err, posts) ->
+            should.not.exist(err)
+            posts.length.should.equal(1)
+            posts[0].content.should.equal('AAA')
+            done()
+
+
+      context 'using flags', () ->
+        beforeEach () ->
+          addSpy () ->
+            sinon.stub(console, 'warn')
+
+        afterEach () ->
+          removeSpy () ->
+            console.warn.restore()
+
+
+        it 'should work', (done) ->
+          Post.find {where: {content: {regexp: /^a/i}}}, (err, posts) ->
+            should.not.exist(err)
+            posts.length.should.equal(1)
+            posts[0].content.should.equal('AAA')
+            done()
+
+        it 'should print a warning when the global flag is set', (done) ->
+            Post.find {where: {content: {regexp: /^a/g}}}, (err, posts) ->
+              console.warn.calledOnce.should.be.ok
+              done()
+
+    context 'with regex object', () ->
+      context 'using no flags', () ->
+        it 'should work', (done) ->
+          Post.find {where: {content: {regexp: new RegExp(/^A/)}}}, (err, posts) ->
+            should.not.exist(err)
+            posts.length.should.equal(1)
+            posts[0].content.should.equal('AAA')
+            done()
+
+
+    context 'using flags', () ->
+      beforeEach () ->
+        addSpy () ->
+          sinon.stub(console, 'warn')
+
+      afterEach () ->
+        removeSpy () ->
+          console.warn.restore()
+
+
+      it 'should work', (done) ->
+        Post.find {where: {content: {regexp: new RegExp(/^a/i)}}}, (err, posts) ->
+          should.not.exist(err)
+          posts.length.should.equal(1)
+          posts[0].content.should.equal('AAA')
+          done()
+
+      it 'should print a warning when the global flag is set', (done) ->
+        Post.find {where: {content: {regexp: new RegExp(/^a/g)}}}, (err, posts) ->
+          should.not.exist(err)
+          console.warn.calledOnce.should.be.ok;
+          done()
+
+  after (done) ->
+    User.destroyAll ->
+      Post.destroyAll ->
+        PostWithNumberId.destroyAll ->
+          PostWithNumberUnderscoreId.destroyAll(done)


Datafield: Small change in render method to fix a problem where disabled was set to true in the initialization. See #51583

--- src/base/DataField/DataField.coffee
+++ src/base/DataField/DataField.coffee
@@ -339,11 +339,11 @@
 		# 	@append(content, p)

 		@__isRendered = true
+		@callOnOthers("render")
 		if @isDisabled()
 			@disable()
 		if @isHidden()
 			@hide(true)
-		@callOnOthers("render")
 		@_onRender?(@)
 		@