Use AutoUpdateManager

Use AutoUpdateManager

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -1,11 +1,10 @@
 AtomWindow = require './atom-window'
 ApplicationMenu = require './application-menu'
 AtomProtocolHandler = require './atom-protocol-handler'
+AutoUpdateManager = require './auto-update-manager'
 BrowserWindow = require 'browser-window'
 Menu = require 'menu'
-autoUpdater = require 'auto-updater'
 app = require 'app'
-dialog = require 'dialog'
 fs = require 'fs'
 ipc = require 'ipc'
 path = require 'path'
@@ -68,11 +67,11 @@

     @applicationMenu = new ApplicationMenu(@version)
     @atomProtocolHandler = new AtomProtocolHandler(@resourcePath)
+    @autoUpdateManager = new AutoUpdateManager()

     @listenForArgumentsFromNewProcess()
     @setupJavaScriptArguments()
     @handleEvents()
-    @setupAutoUpdater()

     @openWithOptions(options)

@@ -126,46 +125,6 @@
   # Configures required javascript environment flags.
   setupJavaScriptArguments: ->
     app.commandLine.appendSwitch 'js-flags', '--harmony'
-
-  # Enable updates unless running from a local build of Atom.
-  setupAutoUpdater: ->
-    return if /\w{7}/.test(@version) # Only released versions should check for updates.
-
-    autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}"
-
-    autoUpdater.on 'checking-for-update', =>
-      @applicationMenu.showDownloadingUpdateItem(false)
-      @applicationMenu.showInstallUpdateItem(false)
-      @applicationMenu.showCheckForUpdateItem(false)
-
-    autoUpdater.on 'update-not-available', =>
-      @applicationMenu.showCheckForUpdateItem(true)
-
-    autoUpdater.on 'update-available', =>
-      @applicationMenu.showDownloadingUpdateItem(true)
-
-    autoUpdater.on 'update-downloaded', (event, releaseNotes, releaseVersion, releaseDate, releaseURL) =>
-      atomWindow.sendCommand('window:update-available', [releaseVersion, releaseNotes]) for atomWindow in @windows
-      @applicationMenu.showInstallUpdateItem(true)
-
-    autoUpdater.on 'error', (event, message) =>
-      @applicationMenu.showCheckForUpdateItem(true)
-
-    # Check for update after Atom has fully started and the menus are created
-    setTimeout((-> autoUpdater.checkForUpdates()), 5000)
-
-  checkForUpdate: ->
-    @onUpdateNotAvailable ?= =>
-      autoUpdater.removeListener 'error', @onUpdateError
-      dialog.showMessageBox type: 'info', buttons: ['OK'], message: 'No update available.', detail: "Version #{@version} is the latest version."
-
-    @onUpdateError ?= (event, message) =>
-      autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable
-      dialog.showMessageBox type: 'warning', buttons: ['OK'], message: 'There was an error checking for updates.', detail: message
-
-    autoUpdater.once 'update-not-available', @onUpdateNotAvailable
-    autoUpdater.once 'error', @onUpdateError
-    autoUpdater.checkForUpdates()

   # Registers basic application commands, non-idempotent.
   handleEvents: ->
@@ -178,7 +137,7 @@
     @on 'application:open-dev', -> @promptForPath(devMode: true)
     @on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y)
     @on 'application:open-documentation', -> shell.openExternal('https://atom.io/docs/latest/?app')
-    @on 'application:install-update', -> autoUpdater.quitAndInstall()
+    @on 'application:install-update', -> @autoUpdateManager.install()
     @on 'application:check-for-update', => @checkForUpdate()

     if process.platform is 'darwin'


updated of rAF.coffee - commit. ⚡

updated of rAF.coffee - commit. ⚡

build through node.js, https://nodejs.org/

--- src/coffee/module/helper/rAF.coffee
+++ src/coffee/module/helper/rAF.coffee
@@ -1,2 +1,48 @@
 # requestAnimationFrame CoffeeScript #
 # The Window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes as an argument a callback to be invoked before the repaint. #
+###!
+# requestAnimationFrame Polyfill
+# ==============================
+# requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel.
+# http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+# http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+# https://gist.github.com/paulirish/1579671
+# MIT license.
+# =========================================
+###
+
+do ->
+  lastTime = 0
+  vendors = [
+    'ms'
+    'moz'
+    'webkit'
+    'o'
+  ]
+  x = 0
+  while x < vendors.length and !window.requestAnimationFrame
+    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']
+    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] or window[vendors[x] + 'CancelRequestAnimationFrame']
+    ++x
+  if !window.requestAnimationFrame
+
+    window.requestAnimationFrame = (callback, element) ->
+      currTime = (new Date).getTime()
+      timeToCall = Math.max(0, 16 - (currTime - lastTime))
+      id = window.setTimeout((->
+        callback currTime + timeToCall
+        return
+      ), timeToCall)
+      lastTime = currTime + timeToCall
+      id
+
+  if !window.cancelAnimationFrame
+
+    window.cancelAnimationFrame = (id) ->
+      clearTimeout id
+      return
+
+  return
+
+# --------------------
+# generated by node.js


Use compress module in s3client service

Use compress module in s3client service

--- src/coffee/services/s3client.coffee
+++ src/coffee/services/s3client.coffee
@@ -6,6 +6,7 @@
 Lynx = require 'lynx'
 Promise = require 'bluebird'
 easyimage = require 'easyimage'
+Compress = require '../compress'
 {CustomError} = require '../errors'
 fs = Promise.promisifyAll require('fs')

@@ -190,14 +191,15 @@
   ###*
    * @private
    *
-   * Resizes and uploads a given image to the bucket
+   * Resizes, optionally compress and uploads a given image to the bucket
    * @param  {String} image The path the the image
    * @param  {String} prefix A prefix for the image key
    * @param  {Array} formats A list of formats for image resizing
    * @param  {String} [tmpDir] A path to a tmp folder
+   * @param  {Boolean} minify Information if compressing is required
    * @return {Promise} A promise, fulfilled with the upload response or rejected with an error
   ###
-  _resizeAndUploadImage: (image, prefix, formats, tmpDir = '/tmp') ->
+  _resizeCompressAndUploadImage: (image, prefix, formats, tmpDir = '/tmp', minify) ->

     extension = path.extname image
     basename = path.basename image, extension
@@ -213,6 +215,9 @@
         dst: tmp_resized
         width: format.width
         height: format.height
+      .then (image) ->
+        if minify
+          Compress.compressImage(tmp_resized, tmpDir, extension)
       .then (image) =>
         @sendMetrics 'increment', 'image.resized'
         header = 'x-amz-acl': 'public-read'
@@ -226,14 +231,14 @@
     , {concurrency: 2}

   ###*
-   * Resizes and uploads a list of images to the bucket
-   * Internally calls {@link _resizeAndUploadImage}
+   * Resizes, optionally compress and uploads a list of images to the bucket
+   * Internally calls {@link _resizeCompressAndUploadImage}
    * @param  {Array} images A list of images to be processed
    * @param  {Object} description A config JSON object describing the images conversion
    * @param  {String} [tmpDir] A path to a tmp folder
    * @return {Promise} A promise, fulfilled with a successful response or rejected with an error
   ###
-  resizeAndUploadImages: (images, description, tmpDir = '/tmp') ->
+  resizeCompressAndUploadImages: (images, description, tmpDir = '/tmp', minify) ->

     Promise.map images, (image) =>
       name = path.basename(image.Key)
@@ -247,7 +252,8 @@
           response.pipe stream
           response.on 'end', resolve
           response.on 'error', reject
-      .then => @_resizeAndUploadImage image.Key, description.prefix, description.formats, tmpDir
+      .then => @_resizeCompressAndUploadImage image.Key, description.prefix, description.formats,
+        tmpDir, minify
       .then (result) =>
         source = "#{description.prefix_unprocessed}/#{name}"
         target = "#{description.prefix_processed}/#{name}"


Try to execute most probable path first

Try to execute most probable path first

--- src/compilers/BinaryCompiler.coffee
+++ src/compilers/BinaryCompiler.coffee
@@ -88,7 +88,11 @@
 	else
 		"""
 			const length_#{id} = programState.getLength()
-			#{generate 0, codes.length - 1}
+			if (length_#{id} < #{codes.length - 1}) {
+				#{generate 0, codes.length - 2}
+			} else {
+				#{codes[codes.length - 1]}
+			}
 		"""


Return early for Clicked read more

Return early for Clicked read more

--- src/desktop/assets/analytics.coffee
+++ src/desktop/assets/analytics.coffee
@@ -11,7 +11,6 @@

 # All Reaction events are sent directly to Segment
 Events.onEvent (data) =>
-  analytics.track data.action, _.omit data, 'action'

   # Send Reaction's read more as a Parsely page view
   if data.action is 'Clicked read more'
@@ -27,6 +26,11 @@
         track_url: true,
         url: sd.APP_URL + '/' + location.pathname,
         use_stored_tags: true
+
+    # Return early because we don't want to make a Segment call for read more
+    return
+
+  analytics.track data.action, _.omit data, 'action'

 require '../analytics/main_layout.js'


Have dictionary working

Have dictionary working

--- src/backside/updates_graph.coffee
+++ src/backside/updates_graph.coffee
@@ -15,6 +15,7 @@
       date_regex = /[0-9]{4}-[0-9]+-[0-9]+/g
       matches = []
       date_array = []
+      dict = {}
       i = 0
       j = 0
       all_files = FileSys.files
@@ -31,7 +32,17 @@
         i++
       while j < date_array.length
         if date_array[j] != null
-          console.log(date_array[j])
-      # console.log(sync_date)
-      # console.log(matches[i])
+          date = date_array[j][0]
+          #console.log(date)
+          split_date = date.split "-"
+          month = split_date[1]
+          #console.log(month)
+        #console.log(dict[month])
+
+          if !dict[month]
+            dict[month] = 1
+            console.log(dict[month])
+          else
+            dict[month]+=1
         j++
+      console.log(dict)


Added hosted cards translation

Added hosted cards translation

--- src/coffee/models/markdown.coffee
+++ src/coffee/models/markdown.coffee
@@ -32,6 +32,7 @@
     if (amount = card.get('genericTokens'))
       cardTokens.push(@tokens('generic', amount))
     opts.tokens = cardTokens unless cardTokens.length == 0
+    opts.hosted = @cards(card.hostedCards().map((card) => @cardToViewModel(card)))
     @faceUpCard(card.get('code'), opts)

   translate: ->


Change logger.info to logger.debug

Change logger.info to logger.debug

Addresses PR comment

--- src/middleware/authorisation.coffee
+++ src/middleware/authorisation.coffee
@@ -137,7 +137,7 @@
     if config.authentication.enableBasicAuthentication
       ctx.set "WWW-Authenticate", "Basic"
     logger.info "The request, '" + ctx.request.path + "', is not authorised to access any channels."
-    auditing.sendAuditEvent genAuthAudit(ctx.ip), -> logger.info 'Processed nodeAuthentication audit'
+    auditing.sendAuditEvent genAuthAudit(ctx.ip), -> logger.debug 'Processed nodeAuthentication audit'
     return done()

 exports.koaMiddleware = (next) ->


replace space to hyphen in search box

replace space to hyphen in search box

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -110,7 +110,8 @@
   return searchResults

 camelToSnake = (str) -> str.replace(/\W+/g, '_').replace(/([a-z\d])([A-Z])/g, '$1-$2')
-
+spaceToHyphen = (str) -> str.replace(/\s/g, '-')
+
 Template._bootstrap_magic.helpers
   "categories" : ->  _.map _.groupBy(bootstrap_magic_variables, 'category'), (obj) ->  obj[0]
   "subCategories" : getCurrentCategory
@@ -124,7 +125,7 @@

 Template._bootstrap_magic.events
   'keyup .search-input' : (e) ->
-    BootstrapMagic.dictionary.searchTerms.set e.currentTarget.value
+    BootstrapMagic.dictionary.searchTerms.set spaceToHyphen(e.currentTarget.value)

   'change .bootstrap-magic-input' : (e) ->
     $input = $(e.currentTarget)


console returns the search terms including any words that contain any of the string in the search box. there is an debug exception though.

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -81,16 +81,16 @@
   return _.where(bootstrap_magic_variables, { category: myCat })

 getCurrentVariables = ->
-  subCatId = BootstrapMagic.dictionary.currentSubCategory.get()
-  return _.find bootstrap_magic_variables, (group) -> group._id is subCatId
-
-getSearched = ->
-  console.log "fm: ", flattenedMagic[item]
-  console.log "fmv: ", flattenedMagicValues
-
-  items = _.map flattenedMagic, mapVariableOverrides
-  searchFilter = _.filter items, (obj) => obj._id == searchTerms.get()
-  console.log "searched: ", searchFilter
+  if !searchTerms.get()
+    subCatId = BootstrapMagic.dictionary.currentSubCategory.get()
+    return _.find bootstrap_magic_variables, (group) -> group._id is subCatId
+  else
+    words = searchTerms.get()
+    items = _.map flattenedMagic, mapVariableOverrides
+    searchFilter = _.filter items, (obj) => obj._id?.indexOf(words) >- 1
+    console.log "searched: ", searchFilter
+    return searchFilter
+

 Template._bootstrap_magic.onCreated ->
   BootstrapMagic.start() if BootstrapMagic.start
@@ -112,12 +112,12 @@
   "previewTmpl" : -> Template["bootstrap_magic_preview_#{camelToSnake @_id}"] || null
   "inputTmpl" : -> Template["bootstrap_magic_input_#{@type}"] || null
   "typeIs" : (type) -> @type is type
-  "searchInactive" : -> !searchTerms.get()
+  "searchInactive" : -> !searchTerms.get()

 Template._bootstrap_magic.events
   'keyup .magic-search' : (e) ->
     searchTerms.set e.currentTarget.value
-    getSearched()
+    getCurrentVariables()

   'click .magic-filter-item' :(e) ->
     $filter = $(e.currentTarget)


Increase repeatLimits.

Increase repeatLimits.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -281,7 +281,7 @@
   copyCurrentUrl: ["Copy the current URL to the clipboard", { noRepeat: true }]
   "LinkHints.activateModeToCopyLinkUrl": ["Copy a link URL to the clipboard", { noRepeat: true }]
   openCopiedUrlInCurrentTab: ["Open the clipboard's URL in the current tab", { background: true }]
-  openCopiedUrlInNewTab: ["Open the clipboard's URL in a new tab", { background: true, repeatLimit: 3 }]
+  openCopiedUrlInNewTab: ["Open the clipboard's URL in a new tab", { background: true, repeatLimit: 20 }]

   enterInsertMode: ["Enter insert mode", { noRepeat: true }]

@@ -316,10 +316,10 @@
   firstTab: ["Go to the first tab", { background: true }]
   lastTab: ["Go to the last tab", { background: true }]

-  createTab: ["Create new tab", { background: true, repeatLimit: 2 }]
-  duplicateTab: ["Duplicate current tab", { background: true, repeatLimit: 2 }]
-  removeTab: ["Close current tab", { background: true, repeatLimit: 3 }]
-  restoreTab: ["Restore closed tab", { background: true, repeatLimit: 3 }]
+  createTab: ["Create new tab", { background: true, repeatLimit: 20 }]
+  duplicateTab: ["Duplicate current tab", { background: true, repeatLimit: 20 }]
+  removeTab: ["Close current tab", { background: true, repeatLimit: 50 }]
+  restoreTab: ["Restore closed tab", { background: true, repeatLimit: 20 }]

   moveTabToNewWindow: ["Move tab to new window", { background: true }]
   togglePinTab: ["Pin/unpin current tab", { background: true }]


Add LinkHints.activateModeToDownloadLink to command list

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -86,23 +86,63 @@
   # be shown in the help page.
   commandGroups:
     pageNavigation:
-      ["scrollDown", "scrollUp", "scrollLeft", "scrollRight", "scrollToTop", "scrollToBottom", "scrollToLeft",
-      "scrollToRight", "scrollPageDown", "scrollPageUp", "scrollFullPageUp", "scrollFullPageDown", "reload",
-      "toggleViewSource", "copyCurrentUrl", "LinkHints.activateModeToCopyLinkUrl",
-      "openCopiedUrlInCurrentTab", "openCopiedUrlInNewTab", "goUp", "goToRoot", "enterInsertMode",
-      "focusInput", "LinkHints.activateMode", "LinkHints.activateModeToOpenInNewTab",
-      "LinkHints.activateModeToOpenInNewForegroundTab", "LinkHints.activateModeWithQueue", "Vomnibar.activate",
-      "Vomnibar.activateInNewTab", "Vomnibar.activateTabSelection", "Vomnibar.activateBookmarks",
-      "Vomnibar.activateBookmarksInNewTab", "goPrevious", "goNext", "nextFrame", "Marks.activateCreateMode",
-      "Vomnibar.activateEditUrl", "Vomnibar.activateEditUrlInNewTab",
+      ["scrollDown",
+      "scrollUp",
+      "scrollLeft",
+      "scrollRight",
+      "scrollToTop",
+      "scrollToBottom",
+      "scrollToLeft",
+      "scrollToRight",
+      "scrollPageDown",
+      "scrollPageUp",
+      "scrollFullPageUp",
+      "scrollFullPageDown",
+      "reload",
+      "toggleViewSource",
+      "copyCurrentUrl",
+      "LinkHints.activateModeToCopyLinkUrl",
+      "openCopiedUrlInCurrentTab",
+      "openCopiedUrlInNewTab",
+      "goUp",
+      "goToRoot",
+      "enterInsertMode",
+      "focusInput",
+      "LinkHints.activateMode",
+      "LinkHints.activateModeToOpenInNewTab",
+      "LinkHints.activateModeToOpenInNewForegroundTab",
+      "LinkHints.activateModeWithQueue",
+      "LinkHints.activateModeToDownloadLink",
+      "Vomnibar.activate",
+      "Vomnibar.activateInNewTab",
+      "Vomnibar.activateTabSelection",
+      "Vomnibar.activateBookmarks",
+      "Vomnibar.activateBookmarksInNewTab",
+      "goPrevious",
+      "goNext",
+      "nextFrame",
+      "Marks.activateCreateMode",
+      "Vomnibar.activateEditUrl",
+      "Vomnibar.activateEditUrlInNewTab",
       "Marks.activateGotoMode"]
     findCommands: ["enterFindMode", "performFind", "performBackwardsFind"]
     historyNavigation:
       ["goBack", "goForward"]
     tabManipulation:
-      ["nextTab", "previousTab", "firstTab", "lastTab", "createTab", "duplicateTab", "removeTab",
-      "restoreTab", "moveTabToNewWindow", "togglePinTab", "closeTabsOnLeft","closeTabsOnRight",
-      "closeOtherTabs", "moveTabLeft", "moveTabRight"]
+      ["nextTab",
+      "previousTab",
+      "firstTab",
+      "lastTab",
+      "createTab",
+      "duplicateTab",
+      "removeTab",
+      "restoreTab",
+      "moveTabToNewWindow",
+      "togglePinTab",
+      "closeTabsOnLeft","closeTabsOnRight",
+      "closeOtherTabs",
+      "moveTabLeft",
+      "moveTabRight"]
     misc:
       ["showHelp"]

@@ -110,12 +150,26 @@
   # a focused, high-signal set of commands to the new and casual user. Only those truly hungry for more power
   # from Vimium will uncover these gems.
   advancedCommands: [
-    "scrollToLeft", "scrollToRight", "moveTabToNewWindow",
-    "goUp", "goToRoot", "focusInput", "LinkHints.activateModeWithQueue", "LinkHints.activateModeToDownloadLink",
-    "Vomnibar.activateEditUrl", "Vomnibar.activateEditUrlInNewTab",
-    "LinkHints.activateModeToOpenIncognito", "goNext", "goPrevious", "Marks.activateCreateMode",
-    "Marks.activateGotoMode", "moveTabLeft", "moveTabRight",
-    "closeTabsOnLeft","closeTabsOnRight", "closeOtherTabs"]
+    "scrollToLeft",
+    "scrollToRight",
+    "moveTabToNewWindow",
+    "goUp",
+    "goToRoot",
+    "focusInput",
+    "LinkHints.activateModeWithQueue",
+    "LinkHints.activateModeToDownloadLink",
+    "Vomnibar.activateEditUrl",
+    "Vomnibar.activateEditUrlInNewTab",
+    "LinkHints.activateModeToOpenIncognito",
+    "goNext",
+    "goPrevious",
+    "Marks.activateCreateMode",
+    "Marks.activateGotoMode",
+    "moveTabLeft",
+    "moveTabRight",
+    "closeTabsOnLeft",
+    "closeTabsOnRight",
+    "closeOtherTabs"]

 defaultKeyMappings =
   "?": "showHelp"


fix some code style and consistency

fix some code style and consistency

--- lib/BlockView.coffee
+++ lib/BlockView.coffee
@@ -21,12 +21,11 @@
   signedDir: (vector, comp)->
     vector[comp] / Math.abs(vector[comp])

-Cards = ['n', 's', 'e', 'w', 'nw', 'ne', 'se', 'sw']
-ordCards = ['s', 'sw', 'w', 'nw', 'n', 'ne', 'e', 'se']
+Cards = 'n,s,e,w,nw,ne,se,sw'.split ','
+ordCards = 's,sw,w,nw,n,ne,e,se'.split ','

 # BlockView
 class @BlockView extends Backbone.View
-
   className: 'zonard'

   # @params options {object}
@@ -35,12 +34,12 @@
   initialize: ->
     @rCont = new RotateContainerView
     # set tranform-origin css property
-    @rCont.$el.css({'transform-origin': 'left top'})
+    @rCont.$el.css  'transform-origin': 'left top'

     @workspace = @options.workspace
     @$workspace = $ @workspace
     @listenToDragStart()
-    @$el.css({"background-color":"#ff0000"})
+    @$el.css "background-color":"#ff0000"

     # initialize _state object, that will hold informations
     # necessary to determines the block position and rotation
@@ -63,7 +62,6 @@
     dragbar.assignCursor(@_state.angle.rad) for i, dragbar of @rCont.handlerContainer.dragbars

   listenToDragStart: ->
-    #@trigger 'drag:start', {at: at, card: @options.card}
     for handle in @rCont.handlerContainer.handles
       @listenTo handle, 'drag:start', (data)=>
         @trigger 'start:resize'
@@ -104,7 +102,6 @@
   setTransform: (@_transform)->

   # the 3 basic movements
-  #
   # position: {left: x, top: y}
   move: (position)=>
     @$el.css(position)
@@ -115,7 +112,7 @@

   rotate: (angleDeg,position)=>
     @rCont.$el.css
-      transform: "rotate(#{angleDeg}deg"
+      transform: "rotate(#{angleDeg}deg)"
       top: position?.top
       left: position?.left

@@ -143,10 +140,6 @@
     # example of position bound based on the box that bounds
     # the rotateContainer
     @_state.positionBounds =
-      #ox: (box.width - w) / 2
-      #oy: (box.height - h) / 2
-      #x: @workspace.width() - (box.width / 2 + w / 2)
-      #y: @workspace.height() - (box.height / 2 + h / 2)
       ox: -Infinity
       oy: -Infinity
       x: Infinity
@@ -184,21 +177,7 @@
       left: vector.x + @_state.elPosition.left + "px"
       top: vector.y + @_state.elPosition.top + "px"

-    ### to constrain the displacement with bound
-    bounds = @_state.positionBounds
-    if pos.left < bounds.ox
-      pos.left = bounds.ox
-    else if pos.left > bounds.x
-      pos.left = bounds.x
-    if pos.top < bounds.oy
-      pos.top = bounds.oy
-    else if pos.top > bounds.y
-      pos.top = bounds.y
-    ###
     @move(pos)
-    #console.log(@$el.position().left)
-    #console.log(@$el.position().top)
-    @trigger 'change:move', pos
     @

   # @todo events are on document
@@ -231,28 +210,28 @@

     # "original" M
     originalM =
-      x : @_state.rotatedCenter.x - @_state.elDimension.width / 2
-      y : @_state.rotatedCenter.y - @_state.elDimension.height / 2
+      x: @_state.rotatedCenter.x - @_state.elDimension.width / 2
+      y: @_state.rotatedCenter.y - @_state.elDimension.height / 2

     # we now have to figure out the new position of the (0,0)
     # of the zonard:
     cM =
-      x : @_state.elOffset.left - @_state.elCenter.x
-      y : @_state.elOffset.top - @_state.elCenter.y
+      x: @_state.elOffset.left - @_state.elCenter.x
+      y: @_state.elOffset.top - @_state.elCenter.y

     cN =
-      x : cM.x * @_state.angle.cos - cM.y * @_state.angle.sin
-      y : cM.x * @_state.angle.sin + cM.y * @_state.angle.cos
+      x: cM.x * @_state.angle.cos - cM.y * @_state.angle.sin
+      y: cM.x * @_state.angle.sin + cM.y * @_state.angle.cos

     mN =
-      x : cN.x - cM.x
-      y : cN.y - cM.y
+      x: cN.x - cM.x
+      y: cN.y - cM.y

     # preparing and changing css
     @$el.css
-      left : originalM.x + mN.x + @_state.workspaceOffset.left
-      top : originalM.y  + mN.y + @_state.workspaceOffset.top
-    @rCont.$el.css {transform: "rotate(#{@_state.angle.deg}deg)"}
+      left: originalM.x + mN.x + @_state.workspaceOffset.left
+      top: originalM.y  + mN.y + @_state.workspaceOffset.top
+    @rCont.$el.css transform: "rotate(#{@_state.angle.deg}deg)"
     @trigger 'change:rotate', @_state.angle.deg

   _endRotate:=>
@@ -267,14 +246,14 @@
   # local base to obtain the top & left movement
   # the 2 last are for the width & height modification
   coefs:
-    n : [ 0,  1,  0, -1]
-    s : [ 0,  0,  0,  1]
-    e : [ 0,  0,  1,  0]
-    w : [ 1,  0, -1,  0]
-    nw : [ 1,  1, -1, -1]
-    ne : [ 0,  1,  1, -1]
-    se : [ 0,  0,  1,  1]
-    sw : [ 1,  0, -1,  1]
+    n:  [ 0,  1,  0, -1]
+    s:  [ 0,  0,  0,  1]
+    e:  [ 0,  0,  1,  0]
+    w:  [ 1,  0, -1,  0]
+    nw: [ 1,  1, -1, -1]
+    ne: [ 0,  1,  1, -1]
+    se: [ 0,  0,  1,  1]
+    sw: [ 1,  0, -1,  1]

   _calculateResize: (event)=>
     coef = @_state.coef
@@ -319,9 +298,6 @@
       x: localVector.x * coef[0]
       y: localVector.y * coef[1]

-    #mB1.x *= constrain.x
-    #mB1.y *= constrain.y
-
     #translated in the base of the screen, it gives us
     mB0 =
       x: @_state.angle.cos * mB1.x - @_state.angle.sin * mB1.y
@@ -329,10 +305,10 @@

     box = {}
     if constrain.x
-      box.left  =  (mB0.x + @_state.elPosition.left)
-      box.width =  dim.w
+      box.left   = mB0.x + @_state.elPosition.left
+      box.width  = dim.w
     if constrain.y
-      box.top =    (mB0.y + @_state.elPosition.top)
+      box.top    = mB0.y + @_state.elPosition.top
       box.height = dim.h

     @resize(box)
@@ -352,7 +328,7 @@
     @$el.css(box)

     angleDeg = @model.get 'rotate'
-    @rCont.$el.css {transform: "rotate(#{angleDeg}deg)"}
+    @rCont.$el.css transform: "rotate(#{angleDeg}deg)"
     @

@@ -367,27 +343,25 @@
 #     dragbars
 #     handles
 class RotateContainerView extends Backbone.View
-
   className: 'rotateContainer'
+
   initialize: ->
     @handlerContainer = new HandlerContainerView
     @display = new DisplayContainerView

   # @chainable
   render: ->
-    @$el
-      .append(@display.render().el)
-      .append(@handlerContainer.render().el)
+    @$el.append @display.render().el, @handlerContainer.render().el
     @

 # display container that holds the content and the borders of the
 # content
 class DisplayContainerView extends Backbone.View
-
   className: 'displayContainer'
+
   initialize: ->
-    @borders = for card, i in Cards.slice 0, 4
+    @borders = for card, i in Cards[..3]
       new BorderView card: card

   # @chainable
@@ -410,8 +384,9 @@

 class HandlerContainerView extends Backbone.View
   className: 'handlerContainer'
+
   initialize: ->
-    @dragbars = for card, i in Cards.slice(0, 4)
+    @dragbars = for card, i in Cards[..3]
       new DragbarView card: card
     @handles = for card, i in Cards
       new HandleView card: card
@@ -420,22 +395,22 @@

   # @chainable
   render: ->
-    @$el.append(@tracker.render().el)
+    @$el.append @tracker.render().el
     @$el.append dragbar.render().el for dragbar in @dragbars
     @$el.append handle.render().el for handle in @handles
-    @$el.append(@rotateHandle.render().el)
+    @$el.append @rotateHandle.render().el
     @

 class SelectionView extends Backbone.View
   events:
-    'mousedown' : 'start'
+    mousedown: 'start'

   # @params options {object}
   # @params options.card {srting}
   initialize: ->
     @card = @options.card
-    @indexCard = _.indexOf(ordCards,@card)
+    @indexCard = _.indexOf(ordCards, @card)
     @$el.css cursor: @card + '-resize'

   start: (event)->
@@ -446,8 +421,8 @@
     @trigger 'drag:start', {origin: origin, card: @card}

   assignCursor: (angle)=>
-    permut = (@indexCard + Math.floor((angle + Math.PI/8) / (Math.PI/4))) %8
-    permut += 8 if permut<0
+    permut = (@indexCard + Math.floor((angle + Math.PI / 8) / (Math.PI / 4))) % 8
+    permut += 8 if permut < 0
     currentCard = ordCards[permut]
     @el.style.cursor = "#{currentCard}-resize"

@@ -464,10 +439,9 @@
 # the special handler responsible for the rotation
 class RotateHandleView extends Backbone.View
   className: 'handleRotation'
+
   events:
-    'mousedown' : 'start'
-  initialize: ->
-    #@listenTo $el, 'mousedown', =>
+    mousedown: 'start'

   start: (event)->
     event.preventDefault()
@@ -477,12 +451,13 @@
 #This element is here to receive mouse events (clicks)
 class TrackerView extends Backbone.View
   className: 'tracker'
+
   events:
-    'mousedown' : 'start'
+    mousedown: 'start'

   start: (event)->
     event.preventDefault()
     origin =
       x: event.pageX
       y: event.pageY
-    @trigger 'drag:start', {origin: origin}
+    @trigger 'drag:start', origin: origin


Only move folders that exist

Only move folders that exist

--- src/dedupe-package-modules.coffee
+++ src/dedupe-package-modules.coffee
@@ -51,7 +51,10 @@
   # Move Atom packages from ~/.atom/packages/node_modules to  ~/.atom/packages
   movePackagesToPackagesFolder: (packagePaths) ->
     for packagePath in packagePaths
-      fs.renameSync(path.join(@userPackagesDirectory, 'node_modules', path.basename(packagePath)), packagePath)
+      packageName = path.basename(packagePath)
+      nodeModulesPath = path.join(@userPackagesDirectory, 'node_modules', packageName)
+      if fs.isDirectorySync(nodeModulesPath)
+        fs.renameSync(nodeModulesPath, packagePath)

     return


Implement #2, filter columns in insert/update

--- src/dialects/sql-formatter.coffee
+++ src/dialects/sql-formatter.coffee
@@ -77,19 +77,22 @@
     column: (c) ->
         atom = _.firstOrSelf(c)
         alias = _.secondOrNull(c)
-        return @_doAtom(atom, alias, true)
-
-    _doAtom: (atom, alias, addAlias = false) ->
+        return @_doColumnAtom(atom, @_doAliasedColumn, alias)
+
+    _doAliasedColumn: (token, schema, alias) ->
+        s = @_doToken(token, schema)
+        alias = @_doAlias(token, schema, alias)
+        if (alias? && alias != schema?.name)
+            s += " as #{@delimit(alias)}"
+
+        return s
+
+    # A column might be an actual table column, but it could also be an expression,
+    # SQL literal, subquery, etc.
+    _doColumnAtom: (atom, fn = @_doToken, p = null) ->
         token = @tokenizeAtom(atom)
-        schema = @_findColumnSchema(token)
-        s = @_doToken(token, schema)
-
-        if addAlias
-            alias = @_doAlias(token, schema, alias)
-            if (alias? && alias != schema?.name)
-                s += " as #{@delimit(alias)}"
-
-        return s
+        columnSchema = @_findColumnSchema(token)
+        return fn.call(@, token, columnSchema, p)

     _doToken: (token, schema) ->
         if (schema?)
@@ -123,19 +126,19 @@
     naryOp: (op, atoms) ->
         switch op
             when 'isNull'
-                pieces = ("#{@_doAtom(a)} IS NULL" for a in atoms)
+                pieces = ("#{@_doColumnAtom(a)} IS NULL" for a in atoms)
             when 'isntNull', 'isNotNull'
-                pieces = ("#{@_doAtom(a)} IS NOT NULL" for a in atoms)
+                pieces = ("#{@_doColumnAtom(a)} IS NOT NULL" for a in atoms)
             when 'isGood'
                 pieces = []
                 for a in atoms
-                    s = @_doAtom(a)
+                    s = @_doColumnAtom(a)
                     pieces.push("#{s} IS NOT NULL AND LEN(RTRIM(LTRIM(#{s}))) > 0")

         return pieces.join(' AND ')

     binaryOp: (left, op, right) ->
-        l = @_doAtom(left)
+        l = @_doColumnAtom(left)

         sqlOp = operatorAliases[op] ? op.toUpperCase()

@@ -187,7 +190,7 @@
         if call.args.length == 0
             return prologue + epilogue

-        @doList(call.args, @_doAtom, ', ', prologue, epilogue)
+        @doList(call.args, @_doColumnAtom, ', ', prologue, epilogue)

     doList: (collection, fn = @f, separator = ', ', prologue = '', epilogue = '') ->
@@ -310,12 +313,12 @@

     where: (c) -> if c.whereClause? then " WHERE #{(c.whereClause.toSql(@))}"  else ''

-    groupBy: (c) -> @doList(c.groupings, @_doAtom, ', ', ' GROUP BY ')
+    groupBy: (c) -> @doList(c.groupings, @_doColumnAtom, ', ', ' GROUP BY ')

     orderBy: (c) -> @doList(c.orderings, @ordering, ', ', ' ORDER BY ')

     ordering: (o) ->
-        s = @_doAtom(_.firstOrSelf(o))
+        s = @_doColumnAtom(_.firstOrSelf(o))
         dir = if _.secondOrNull(o) == 'DESC' then 'DESC' else 'ASC'

         "#{s} #{dir}"
@@ -323,7 +326,10 @@
     _doTargetTable: (name) ->
         fullName = @parseWhenRawName(name)
         schema = @_findTableSchema(fullName)
-        @sources.push(_schema: schema) if schema?
+        if schema?
+            @sources.push(_schema: schema)
+            @targetTableSchema = schema
+
         return @_doToken(fullName, schema)

     insert: (i) ->
@@ -331,20 +337,32 @@
         names = []
         values = []
         for k, v of i.values
-            names.push(@_doAtom(k))
+            c = @_doColumnAtom(k, @_doInsertUpdateColumn)
+            continue unless c
+            names.push(c)
             values.push(@f(v))

         ret += " (#{names.join(', ')}) VALUES (#{values.join(', ')})"
-

     update: (u) ->
         ret = "UPDATE #{@_doTargetTable(u.targetTable)} SET "

-        values = ("#{@_doAtom(k)} = #{@f(v)}" for k, v of u.values)
+        values = []
+        for k, v of u.values
+            c = @_doColumnAtom(k, @_doInsertUpdateColumn)
+            continue unless c
+            values.push("#{c} = #{@f(v)}")
+
         ret += values.join(', ')

         ret += @where(u)
         return ret
+
+    _doInsertUpdateColumn: (token, columnSchema, s) ->
+        if @targetTableSchema? && (not columnSchema? || columnSchema.isReadOnly)
+            return false
+
+        return @_doToken(token, columnSchema)

     delete: (d) ->
         ret = "DELETE FROM #{@_doTargetTable(d.targetTable)}"


Add condition notification

Add condition notification

--- views/components/ship/index.cjsx
+++ views/components/ship/index.cjsx
@@ -243,7 +243,7 @@
         if countdown[i] > 0
           countdown[i] -= 1
           if countdown[i] is 0
-            notify "#{@state.names[i]} 修复完成", {icon: join(ROOT, 'assets', 'img', 'operation', 'sortie.png')}
+            notify "#{@state.names[i]} 疲劳回复完成", {icon: join(ROOT, 'assets', 'img', 'operation', 'sortie.png')}
       @setState
         countdown: countdown
     componentDidMount: ->


add a check

add a check

--- views/components/ship/index.cjsx
+++ views/components/ship/index.cjsx
@@ -147,8 +147,9 @@
       else
         true
     handleClick: (idx) ->
-      @setState
-        activeDeck: idx
+      if idx isnt @state.activeDeck
+        @setState
+          activeDeck: idx
     handleResponse: (e) ->
       {method, path, body, postBody} = e.detail
       {names, ndocks} = @state


Tests: Environments: VM: Deleted extra test name.

--- tests/src/environments/vm.coffee
+++ tests/src/environments/vm.coffee
@@ -33,7 +33,6 @@
     browser.end()

-  CheckVMDiskUsage: (browser) ->
   checkVMDiskUsage: (browser) ->

     diskUsageSelector     = modalSelector + ' .diskusage'


[ @ ] Refactoring

[ @ ] Refactoring

--- lib/arcabouco.coffee
+++ lib/arcabouco.coffee
@@ -25,12 +25,12 @@

   parseControllerRoutes : ( indexOfController ) ->
     ControllerObject = @controllerInstances[ indexOfController ]
-    return false unless ControllerObject.getRoutes
+    unless ControllerObject.getRoutes
+      return false

     controllerRoutes = ControllerObject.getRoutes()
     for path of controllerRoutes
-      method = controllerRoutes[ path ]
-      @addRoutingToMethod path, method, indexOfController
+      @addRoutingToMethod path, controllerRoutes[ path ], indexOfController

   loadController      : ( controllerFilename ) ->
     unless controllerFilename.match(/\.js$/gi)
@@ -62,10 +62,6 @@
     @avaiableRoutes = []
     for pattern in orderedRouteNames
       @avaiableRoutes.push @contructRoutingForPattern( pattern )
-    if global.debugging
-      console.log 'DEBUG: buildRouting'
-      console.log @avaiableRoutes
-    true

   parseRequest : ( request ) ->
     request.setEncoding 'utf-8'


Tweak documentReady().

Tweak documentReady().

We can remove these listeners once we're done.

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -2,11 +2,13 @@
   #
   # Runs :callback if the DOM has loaded, otherwise runs it on load
   #
-  documentReady: (func) ->
+  documentReady: (callback) ->
     if document.readyState == "loading"
-      window.addEventListener "DOMContentLoaded", func
-    else
-      func()
+      window.addEventListener "DOMContentLoaded", handler = ->
+        window.removeEventListener "DOMContentLoaded", handler
+        callback()
+    else
+      callback()

   createElement: (tagName) ->
     element = document.createElement tagName


add 'is_intrinsic'

add 'is_intrinsic'

--- lib/echo-util.coffee
+++ lib/echo-util.coffee
@@ -59,3 +59,5 @@
         type: syntax.CallExpression
         callee: create_identifier "%#{name}"
         arguments: args
+exports.is_intrinsic = (name, n) ->
+        n.type is syntax.CallExpression and n.callee.name is "%#{name}"


escape = as first character in inlined text. fixes #12

--- lib/html2jade.coffee
+++ lib/html2jade.coffee
@@ -181,6 +181,8 @@
       output.writeln()
       output.leave()
     else if tagText
+      if tagText.length > 0 and tagText.charAt(0) is '='
+        tagText = '\\' + tagText
       output.writeln tagHead + tagAttr + ' ' + tagText
     else
       output.writeln tagHead + tagAttr


fixes

fixes

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -35,12 +35,11 @@
     @unfoldSpeedSub = atom.config.observe 'autohide-tree-view.unfoldSpeed', @applyUnfoldSpeed
     @hideDelaySub = atom.config.observe 'autohide-tree-view.hideDelay', @applyHideDelay
     @minWidthSub = atom.config.observe 'autohide-tree-view.minimizedWidth', @applyMinWidth
-    @extraPaddingSub = atom.config.observe 'autohide-tree-view.extraPadding', @applyPadding

   deactivate: ->
     @unfoldSpeedSub.dispose()
+    @hideDelaySub.dispose()
     @minWidthSub.dispose()
-    @extraPaddingSub.dispose()
     if stylesheet?
       stylesheet.parentNode.removeChild stylesheet
       stylesheet = null


sigh, switch back to terribly pessimistic environment substitution code because set.js fails to compile otherwise

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -137,13 +137,13 @@
                 env = current_env
                 while env?
                         if env.decls.member n.name
-                                if env.closed.member n.name
-                                        n.ejs_substitute = true
-                                else if env isnt current_env
-                                        env.closed.add n.name
-                                        n.ejs_substitute = true
-#                                env.closed.add n.name
-#                                n.ejs_substitute = true
+#                                if env.closed.member n.name
+#                                        n.ejs_substitute = true
+#                                else if env isnt current_env
+#                                        env.closed.add n.name
+#                                        n.ejs_substitute = true
+                                env.closed.add n.name
+                                n.ejs_substitute = true
                                 return n
                         else
                                 env = env.parent


Do not strip extension from URL with extension before finding files.

--- lib/action.coffee
+++ lib/action.coffee
@@ -87,7 +87,7 @@
           else
             oneDone()

-  find_files pathobj.dirname, pathobj.base, (files)->
+  find_files pathobj.dirname, pathobj.basename, (files)->
     if files['/'] && meta._indexResourceName && 1 == Object.keys(files).length
       # A directory was found.  Scan it, looking for _index files
       find_files join(pathobj.dirname,pathobj.basename), meta._indexResourceName, (indexFiles)->


Client: Activity: Added handleFollowChannelSuccess event handler to add given channelId to followedChannelIds

--- client/activity/lib/flux/stores/followedpublicchannelidsstore.coffee
+++ client/activity/lib/flux/stores/followedpublicchannelidsstore.coffee
@@ -12,8 +12,29 @@
   initialize: ->

     @on actions.LOAD_FOLLOWED_PUBLIC_CHANNEL_SUCCESS, @handleLoadChannelSuccess
+    @on actions.FOLLOW_CHANNEL_SUCCESS, @handleFollowChannelSuccess

+  ###*
+   * Adds a new channel to followedChannels by given channelId
+   *
+   * @param {Immutable.Map} followedChannelIds
+   * @param {object} payload
+   * @param {string} payload.channel
+  ###
   handleLoadChannelSuccess: (followedChannelIds, { channel }) ->

     followedChannelIds.set channel.id, channel.id
+
+
+  ###*
+   * Adds a new channel to followedChannels by given channelId
+   *
+   * @param {Immutable.Map} followedChannelIds
+   * @param {object} payload
+   * @param {string} payload.channelId
+  ###
+  handleFollowChannelSuccess: (followedChannelIds, { channelId }) ->
+
+    followedChannelIds.set channelId, channelId
+


Update webhooks script

Update webhooks script

--- src/ac-webhooks.coffee
+++ src/ac-webhooks.coffee
@@ -18,8 +18,14 @@
 module.exports = (robot) ->

   robot.router.post "/hubot/ac-webhooks", (req, res) ->
-    console.log req.body
+    data = JSON.parse req.body['data']
+    console.log data
+    timeRecord = data['time_record']
+    billableStatus = 'billable'
+    if timeRecord['billable_status'] != 1 then billableStatus = 'non billable'
+    userName = timeRecord['user_name']
+    time = timeRecord['value']
     user = {}
     user.room = process.env['HUBOT_CAMPFIRE_ROOMS'].split(',')[0]
-    robot.send user, 'ac webhooks url hit!'
+    robot.send user, userName + ' logged ' + time + ' ' + billableStatus + ' hours'
     res.end '{"success":true}'


A bit closer at getting the worker functioning in coffeescript.

--- script/worker.coffee
+++ script/worker.coffee
@@ -4,12 +4,12 @@
 Build       = require('../models/build').Build
 Resque      = require('../config/resque').Connection
 mongoose    = require('../config/mongoose')
-redis       = require('../config/redis')
+Redis       = require('../config/redis').Connection
 ObjectId    = require('mongoose').Types.ObjectId

 Jobs =
   succeed: (arg, callback) ->
-    callback
+    callback()

   fail: (arg, callback) ->
     callback new Error 'fail'
@@ -20,6 +20,7 @@
     cmds            = {}
     outputBuffer    = []
     stepsSuccessful = false
+    workingDir      = ""

     console.log 'Build called; repositoryId: ' + repositoryId + ' buildId: ' + buildId

@@ -27,118 +28,119 @@
       if err
         throw err

-        console.log 'Repository found, id:['+repository._id+'] url:['+repository.url+'], num builds:['+repository.builds.length+']'
+      console.log 'Repository found, id:['+repository._id+'] url:['+repository.url+'], num builds:['+repository.builds.length+']'
+
+      buildRepository = repository
+
+      build = repository.builds.id buildId
+
+      buildStart = ->
+        build.startedAt = Date.now()
+        build.running   = true
+        repository.save (err) ->
+          if err
+            throw err
+
+          spawnCloneDir()
+
+      spawnCloneDir = ->
+        name = 'mkdir'
+        workingDir = ['/tmp/', 'dripio', repository.ownerName, repository.name, Date.now()].join('_')
+        console.log "making directory ["+workingDir+"]..."
+        cmds[name] = spawn('mkdir',['-vp',workingDir])
+        cmdOut.bind(name, spawnClone)
+
+      spawnClone = ->
+        name = 'clone'
+        console.log "cloning ["+repository.url+"]..."
+        cmds[name] = spawn('git', ['clone', repository.url, workingDir], { cwd: workingDir, setsid: false })
+        cmdOut.bind(name, spawnCheckout)
+
+      spawnCheckout = ->
+        name = 'checkout'
+        console.log "checkout ["+build.branch+"]..."
+        cmds[name] = spawn('git', ['checkout',build.branch], {cwd: workingDir, setsid: false})
+        cmdOut.bind(name, spawnNpmInstall)
+
+      spawnNpmInstall = ->
+        name = 'npm_install'
+        console.log("running npm install...")
+        cmds[name] = spawn('npm',['install'], {cwd: workingDir})
+        cmdOut.bind(name, spawnNpmTest)
+
+      spawnNpmTest = ->
+        name = 'npm_test'
+        console.log("running npm test...")
+        cmds[name] = spawn('npm',['test'], {cwd: workingDir})
+        cmdOut.bind(name, buildFinish)
+
+      buildFinish = ->
+        name = 'finish'
+        build.finishedAt = Date.now()
+        console.log("finishing build and cleaning-up ["+build.finishedAt+"]...")
+        build.completed  = true
+        build.running    = false
+        build.successful = stepsSuccessful
+        repository.save (err) ->
+          if err
+            throw err

-        buildRepository = repository
+        cmds[name] = spawn('rm',['-vrf', workingDir])
+        cmdOut.bind(name)

-        build = repository.builds.id buildId
+      cmdOut =
+        bind: (name, next) ->
+          spawn = cmds[name]
+          this.stdout(spawn,name)
+          this.stderr(spawn,name)
+          this.exit(spawn,name,next)

-        buildStart = ->
-          build.startedAt = Date.now()
-          build.running   = true
-          repository.save (err) ->
-            if err
-              throw err
-            spawnCloneDir
+        stdout: (spawn, name) ->
+          spawn.stdout.on 'data', (data) ->
+            console.log 'stdout '+name+' ['+workingDir+']: ' + data
+            Redis.rpush "builds:" + build.id, data, ->
+              console.log "Issued write to redis"
+            outputBuffer.push data

-        spawnCloneDir = ->
-          name = 'mkdir'
-          workingDir = ['/tmp/', 'dripio', repository.ownerName, repository.name, Date.now()].join('_')
-          console.log "making directory ["+workingDir+"]..."
-          cmds[name] = spawn('mkdir',['-vp',workingDir])
-          cmdOut.bind(name, spawnClone)
+        stderr: (spawn, name) ->
+          spawn.stderr.on 'data', (data) ->
+            console.log 'stderr '+name+' ['+workingDir+']: ' + data
+            Redis.rpush "builds:" + build.id, data, ->
+              console.log "Issued write to redis"
+            outputBuffer.push data

-        spawnClone = ->
-          name = 'clone'
-          console.log "cloning ["+repository.url+"]..."
-          cmds[name] = spawn('git', ['clone',repository.url, workingDir], {cwd: workingDir, setsid: false})
-          cmdOut.bind(name, spawnCheckout)
-
-        spawnCheckout = ->
-          name = 'checkout'
-          console.log "checkout ["+build.branch+"]..."
-          cmds[name] = spawn('git', ['checkout',build.branch], {cwd: workingDir, setsid: false})
-          cmdOut.bind(name, spawnNpmInstall)
+        exit: (spawn, name, next) ->
+          spawn.on 'exit', (code) ->
+            console.log 'exit '+name+' ['+workingDir+'] code: ' + code
+
+            buildRepository.save (err) ->
+              if err
+                throw err
+
+            if code == 0
+              console.log 'clean exit; calling next() if present'
+              stepsSuccessful = true
+
+              if typeof(next) == 'function'
+                next()
+            else
+              console.log 'unclean exit; not progressing to next, typeof: '+typeof(next)
+              stepsSuccessful = false
+              build.finishedAt = Date.now()

-        spawnNpmInstall = ->
-          name = 'npm_install'
-          console.log("running npm install...")
-          cmds[name] = spawn('npm',['install'], {cwd: workingDir})
-          cmdOut.bind(name, spawnNpmTest)
-
-        spawnNpmTest = ->
-          name = 'npm_test'
-          console.log("running npm test...")
-          cmds[name] = spawn('npm',['test'], {cwd: workingDir})
-          cmdOut.bind(name, buildFinish)
-
-        buildFinish = ->
-          name = 'finish'
-          build.finishedAt = Date.now()
-          console.log("finishing build and cleaning-up ["+build.finishedAt+"]...")
-          build.completed  = true
-          build.running    = false
-          build.successful = stepsSuccessful
-          repository.save (err) ->
-            if err
-              throw err
-
-          cmds[name] = spawn('rm',['-vrf', workingDir])
-          cmdOut.bind(name)
-
-        cmdOut =
-          bind: (name, next) ->
-            spawn = cmds[name]
-            this.stdout(spawn,name)
-            this.stderr(spawn,name)
-            this.exit(spawn,name,next)
-
-          stdout: (spawn, name) ->
-            spawn.stdout.on 'data', (data) ->
-              console.log 'stdout '+name+' ['+workingDir+']: ' + data
-              redis.rpush "builds:" + build.id, data, ->
-                console.log "Issued write to redis"
-              outputBuffer.push data
-
-          stderr: (spawn, name) ->
-            spawn.stderr.on 'data', (data) ->
-              console.log 'stderr '+name+' ['+workingDir+']: ' + data
-              redis.rpush "builds:" + build.id, data, ->
-                console.log "Issued write to redis"
-              outputBuffer.push data
-
-          exit: (spawn, name, next) ->
-            spawn.on 'exit', (code) ->
-              console.log 'exit '+name+' ['+workingDir+'] code: ' + code
-
-              buildRepository.save (err) ->
+              console.log "finishing build at ["+build.finishedAt+"]..."
+              build.completed  = true
+              build.running    = false
+              build.successful = stepsSuccessful
+              repository.save (err) ->
                 if err
                   throw err
-
-              if code == 0
-                console.log 'clean exit; calling next() if present'
-                stepsSuccessful = true
-
-                if typeof(next) == 'function'
-                  next
-              else
-                console.log 'unclean exit; not progressing to next, typeof: '+typeof(next)
-                stepsSuccessful = false
-                build.finishedAt = Date.now()

-                console.log "finishing build at ["+build.finishedAt+"]..."
-                build.completed  = true
-                build.running    = false
-                build.successful = stepsSuccessful
-                repository.save (err) ->
-                  if err
-                    throw err
+      buildStart()
+
+      console.log 'Build finished.'

-        buildStart
-
-        console.log 'Build finished.'
-
-        callback build
+      callback(build)

 worker        = Resque.worker('builder', Jobs)
 workerCleanup = require('../lib/worker').workerCleanup


I found that my monitors were going red when the value was below the data-green threshold.

For example val = 100, data-green=500 and data-yellow=1000. This resulted in a red background and not a green one as you would expect.

The issue is that the values for data-green and data-yellow that are returned by

```javascript

document.getElementById(this.id).getAttribute('data-green')

```

are String and not numbers. When compared then "500" is in fact greater than "1000". I also found that "100" is less than "1000" because the string is shorter, not because the number the is less.

Parsing the numbers to ints fixed the issue for me.

--- widgets/newrelic/newrelic.coffee
+++ widgets/newrelic/newrelic.coffee
@@ -19,8 +19,8 @@

   onData: (data) ->

-    @responseGreen = document.getElementById(this.id).getAttribute('data-green')
-    @responseYellow = document.getElementById(this.id).getAttribute('data-yellow')
+    @responseGreen = parseInt(document.getElementById(this.id).getAttribute('data-green'))
+    @responseYellow = parseInt(document.getElementById(this.id).getAttribute('data-yellow'))

     val = data.current
     if (


Fix bug in traverse function in device

Fix bug in traverse function in device

--- lib/device.coffee
+++ lib/device.coffee
@@ -270,7 +270,7 @@

     # Collect current properties
     if ref[0]?[0]?
-      for i in [revisionThreshold...Math.min(revision, ref[0][0].length)] by 1 when (rev = ref[0][0][i])?
+      for i in [revisionThreshold...Math.min(revision + 1, ref[0][0].length)] by 1 when (rev = ref[0][0][i])?
         for j in [0...rev.length] by 2 when rev[j]?
           if rev[j] >= timestampThreshold
             current[j] = rev[j]


Open package settings page

Open package settings page

--- lib/package-panel-view.coffee
+++ lib/package-panel-view.coffee
@@ -16,9 +16,12 @@
   addPackage: (pack, timeKey) ->
     @list.append $$ ->
       @li class: 'list-item', =>
-        homepage = pack.metadata?.homepage
-        homepage ?= "https://atom.io/packages/#{pack.name}"
-        @a class: 'inline-block', href: homepage, pack.name
+        @a class: 'inline-block package', 'data-package': pack.name, pack.name
         highlightClass = 'highlight-warning'
         highlightClass = 'highlight-error' if pack[timeKey] > 25
         @span class: "inline-block #{highlightClass}", "#{pack[timeKey]}ms"
+
+  initialize: ->
+    @on 'click', 'a.package', ->
+      packageName = this.dataset['package']
+      atom.workspace.open("atom://config/packages/#{packageName}")


was logging instead of sending

was logging instead of sending

--- scripts/neige.coffee
+++ scripts/neige.coffee
@@ -34,9 +34,9 @@

         delay = (s, i) ->
           setTimeout () ->
-            console.log(s)
+            msg.send s
           , 100 * i
-
+
         for s, i in messages
           delay s, i


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -19,7 +19,7 @@

   fadeOutDefaults =
     color: 'transparent'
-    dur
+    duration: 30

   configureFade = (duration, color, alpha) ->
     I.flashDuration = duration


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -17,7 +17,7 @@
     duration: 30

   fadeOutDefaults =
-
+    alpha:
     color: 'transparent'
     duration: 30


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -10,7 +10,7 @@
 ###
 Camera.Fade = (I, self) ->
   fadeInDefaults =
-
+    alpha: 0
     color: 'black'
     duration: 30


NAM-46 開発基盤:CI環境作成

NAM-46 開発基盤:CI環境作成

カバレッジレポート形式をlcovに変更
担当：阪口

--- www_dev/build/tasks/karma.coffee
+++ www_dev/build/tasks/karma.coffee
@@ -33,8 +33,16 @@
         "app/**/*.js": "coverage"

       coverageReporter:
-        type: "clover"
-        dir: "reports/coverage"
+        reporters: [
+          {
+            type: "clover"
+            dir: "reports/coverage"
+          }
+          {
+            type: "lcov"
+            dir: "reports/coverage"
+          }
+        ]

       junitReporter:
         outputFile: "reports/test/test-results.xml"


Fixed remove method for delete first item of a collection

--- lib/somewhere.coffee
+++ lib/somewhere.coffee
@@ -46,7 +46,7 @@
   remove: (collection, id) ->
     _checkCollection @database, collection
     index = @database[collection].indexOf(item) for item in @database[collection] when item.id is id
-    return false unless index
+    return false if not index and index isnt 0
     @database[collection].splice index, 1 if index > -1
     do @write
     true


removed redundant command

removed redundant command

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -34,8 +34,5 @@
 	robot.respond /grazie/i, (res) ->
 		res.reply 'Prego, dovere'

-	robot.respond /elena/i, (res) ->
-		res.reply 'Tutto qui?\n... prova a vedere qui: https://www.google.it/search?q=elena+riz&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwjrqqrE7dvKAhWEPhQKHaHhDYkQsAQIHw&biw=1920&bih=507#tbm=isch&q=elena+riz\n\nSe ne vuoi qualcuna nuda... basta chiedere!'
-
 	robot.respond /elena (.*)/i, (res) ->
 		res.reply 'Brutto porco!\n... prova a vedere qui: https://www.google.it/search?q=elena+riz&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwjrqqrE7dvKAhWEPhQKHaHhDYkQsAQIHw&biw=1920&bih=507#tbm=isch&q=elena+riz+'+res.match[1]


Disable auto-save when a save is pending.

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -191,6 +191,9 @@
             return @_error(err) if err
             @_fileChanged 'openedFile', content, metadata, {openedContent: content.clone()}, @_getHashParams metadata

+  isSaveInProgress: ->
+    @state.saving?
+
   save: (callback = null) ->
     @_event 'getContent', {}, (stringContent) =>
       @saveContent stringContent, callback
@@ -208,7 +211,7 @@
       currentContent = @_createOrUpdateCurrentContent stringContent, metadata
       metadata.provider.save currentContent, metadata, (err) =>
         if err
-          # disable autosave on save failure; clear "saving..." message
+          # disable autosave on save failure; clear "Saving..." message
           metadata.autoSaveDisabled = true
           @_setState { metadata: metadata, saving: null }
           return @_error(err)
@@ -362,11 +365,14 @@
     if @_autoSaveInterval
       clearInterval @_autoSaveInterval

+    shouldAutoSave = =>
+      @state.dirty and not @state.metadata?.autoSaveDisabled and not @isSaveInProgress() and @state.metadata?.provider?.can 'save'
+
     # in case the caller uses milliseconds
     if interval > 1000
       interval = Math.round(interval / 1000)
     if interval > 0
-      @_autoSaveInterval = setInterval (=> @save() if @state.dirty and not @state.metadata?.autoSaveDisabled and @state.metadata?.provider?.can 'save'), (interval * 1000)
+      @_autoSaveInterval = setInterval (=> @save() if shouldAutoSave()), (interval * 1000)

   isAutoSaving: ->
     @_autoSaveInterval?


Typo in comment

Typo in comment

--- lib/traverser.coffee
+++ lib/traverser.coffee
@@ -139,7 +139,7 @@
       previous = @history[@history.length-1]

       switch previous?.constructor.name
-        # A comment is preveding the entity declaration
+        # A comment is preceding the entity declaration
         when 'Comment'
           doc = previous


Append using DOM APIs

Append using DOM APIs

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -220,7 +220,7 @@
       })
       @root = new DirectoryView()
       @root.initialize(directory)
-      @list.append(@root)
+      @list.element.appendChild(@root)

       if @attachAfterProjectPathSet
         @attach()


Add SMILES mode

Add SMILES mode

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -1,6 +1,8 @@
 $(document).ready ->
   screenWidth = window.screen.availWidth
   screenHeight = window.screen.availHeight
+
+  smilesMode = true

   foregroundColor = 'ffffff'
   backgroundColor = '000000'
@@ -80,19 +82,21 @@

   $('.picker-fg').on 'change', (e) ->
     foregroundColor = $(e.target).val().substring(1)
-    refreshPreview()
+    if smilesMode then refreshPreviewSmiles() else refreshPreview()

   $('.picker-bg').on 'change', (e) ->
     backgroundColor = $(e.target).val().substring(1)
-    refreshPreview()
+    if smilesMode then refreshPreviewSmiles() else refreshPreview()

   # Compound name update button should refresh the preview.

   $('.update-btn').on 'click', (e) ->
+    smilesMode = false
     errorRows.hide()
     checkMoleculeName getCompoundName(), refreshPreview, failPreview

   $('.update-smiles-btn').on 'click', (e) ->
+    smilesMode = true
     errorRows.hide()
     checkSmiles getCompoundSmiles(), refreshSmilesPreview, failPreviewSmiles

@@ -101,5 +105,5 @@
   $('.download-btn').on 'click', (e) ->
     url = buildUrl screenWidth, screenHeight, foregroundColor, backgroundColor, compoundName
     window.open url
-
+
   refreshSmilesPreview() # Initial update.


Use skinny arrow

Use skinny arrow

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -154,7 +154,7 @@
   resizeTreeView: ({pageX}) =>
     @width(pageX)

-  resizeToFitContent: =>
+  resizeToFitContent: ->
     treeViewWidth = @find('.tree-view.list-tree').outerWidth() + 15
     @width(treeViewWidth) if treeViewWidth > @width()


Update to new atom.confirm API

Update to new atom.confirm API

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -258,13 +258,13 @@
     return unless entry

     entryType = if entry instanceof DirectoryView then "directory" else "file"
-    atom.confirm(
-      "Are you sure you would like to delete the selected #{entryType}?",
-      "You are deleting #{entry.getPath()}",
-      "Move to Trash", (=> shell.moveItemToTrash(entry.getPath())),
-      "Cancel", null
-      "Delete", (=> fs.removeSync(entry.getPath()))
-    )
+    atom.confirm
+      message: "Are you sure you want to delete the selected #{entryType}?"
+      detailedMessage: "You are deleting #{entry.getPath()}"
+      buttons:
+        "Move to Trash": -> shell.moveItemToTrash(entry.getPath())
+        "Cancel": null
+        "Delete": -> fs.removeSync(entry.getPath())

   add: ->
     selectedEntry = @selectedEntry() or @root


Not hours either. Something has broken points.

--- scripts/watched.coffee
+++ scripts/watched.coffee
@@ -9,70 +9,70 @@
 # Author:
 #   Derived from points.coffee by brettlangdon

-#hours = {}
-#timename = {}
-#timepeople = {}
-#hourstotal = {}
-#
-## Util = require "util"
-#
-#flatten = (array) ->
-#    Array::concat.apply([], array)
-#
-#award_hours = (msg, timename, hrs) ->
-#    hours[timename] ?= 0
-#    hours[timename] += parseInt(hrs)
-#
-#save = (robot) ->
-#    robot.brain.data.hours = hours
-#
-#module.exports = (robot) ->
-#    robot.brain.on 'loaded', ->
-#        hours = robot.brain.data.hours or {}
-#
-#    # Compensate for resets and dropout. Name first. 100 = 1hr
-#    robot.respond /drop (.*?) (\d+)/i, (msg) ->
-#        if robot.auth.hasRole(msg.envelope.user, ['admin'])
-#            award_hours(msg, msg.match[1], msg.match[2])
-#            save(robot)
-#            return
-#
-#    # Give users 5 points each 3 mins which is 100/hr
-#    setInterval (->
-#        robot.http("https://tmi.twitch.tv/group/user/masonest/chatters").get() (err, res, body) ->
-#            chat = JSON.parse(body)
-#            timepeople = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers]).filter((watchers) ->
-#                watchers != 'awebot'
-#            )
-#
-#            for timename in timepeople
-#                hours[timename] ?= 0
-#                hours[timename] += 5
-#                save(robot)
-#
-#            # Store in brain for leaderboards later.
-#            robot.brain.set 'topwatchers', hours
-#    ), 180000
-#
-#    # Admin and mods can check the hours of others
-#    robot.respond /hours ([a-zA-Z0-9_]*)/i, (msg) ->
-#        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
-#            timename = msg.match[1].toLowerCase()
-#            hours[timename] ?= 0
-#            hourstotal = hours[timename] / 100
-#
-#            msg.send timename + ' has spent ' + hourstotal + ' hours in the channel!'
-#            return
-#
-#        msg.send "Only mods can check the hours of others!"
-#
-#    # Allows viewers to check their own hours watched.
-#    robot.respond /hours$/i, (msg) ->
-#        timename = "#{msg.envelope.user.name}"
-#        hours[timename] ?= 0
-#        hourstotal = hours[timename] / 100
-#
-#        msg.send "#{msg.envelope.user.name}, you have spent " + hourstotal + " hours in the channel!"
+hours = {}
+timename = {}
+timepeople = {}
+hourstotal = {}
+
+# Util = require "util"
+
+flatten = (array) ->
+    Array::concat.apply([], array)
+
+award_hours = (msg, timename, hrs) ->
+    hours[timename] ?= 0
+    hours[timename] += parseInt(hrs)
+
+save = (robot) ->
+    robot.brain.data.hours = hours
+
+module.exports = (robot) ->
+    robot.brain.on 'loaded', ->
+        hours = robot.brain.data.hours or {}
+
+    # Compensate for resets and dropout. Name first. 100 = 1hr
+    robot.respond /drop (.*?) (\d+)/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+            award_hours(msg, msg.match[1], msg.match[2])
+            save(robot)
+            return
+
+    # Give users 5 points each 3 mins which is 100/hr
+    setInterval (->
+        robot.http("https://tmi.twitch.tv/group/user/masonest/chatters").get() (err, res, body) ->
+            chat = JSON.parse(body)
+            timepeople = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers]).filter((watchers) ->
+                watchers != 'awebot'
+            )
+
+            for timename in timepeople
+                hours[timename] ?= 0
+                hours[timename] += 5
+                save(robot)
+
+            # Store in brain for leaderboards later.
+            robot.brain.set 'topwatchers', hours
+    ), 180000
+
+    # Admin and mods can check the hours of others
+    robot.respond /hours ([a-zA-Z0-9_]*)/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+            timename = msg.match[1].toLowerCase()
+            hours[timename] ?= 0
+            hourstotal = hours[timename] / 100
+
+            msg.send timename + ' has spent ' + hourstotal + ' hours in the channel!'
+            return
+
+        msg.send "Only mods can check the hours of others!"
+
+    # Allows viewers to check their own hours watched.
+    robot.respond /hours$/i, (msg) ->
+        timename = "#{msg.envelope.user.name}"
+        hours[timename] ?= 0
+        hourstotal = hours[timename] / 100
+
+        msg.send "#{msg.envelope.user.name}, you have spent " + hourstotal + " hours in the channel!"

     #
     # We don’t need the following until it's fully


Add more logic on preview in tab

Add more logic on preview in tab

--- lib/viewer.coffee
+++ lib/viewer.coffee
@@ -48,6 +48,9 @@
     if !@getUrl()
       return

+    if @tabView?
+      atom.workspace.paneForItem(@tabView).destroyItem(@tabView)
+      @tabView = undefined
     if !@window? or @window.isDestroyed()
       @window = new BrowserWindow()
     else
@@ -70,9 +73,11 @@
     if !@getUrl()
       return

-    pane = atom.workspace.getActivePane().splitRight()
-    @tabView = new PDFView(@url)
-    pane.addItem(@tabView)
+    if @tabView?
+      atom.workspace.paneForItem(@tabView).activateItem(@tabView)
+    else
+      @tabView = new PDFView(@url)
+      atom.workspace.getActivePane().splitRight().addItem(@tabView)

   getUrl: ->
     try


Compatibility with latest Backbone and VIE

--- lib/vie-redis.coffee
+++ lib/vie-redis.coffee
@@ -1,78 +1,76 @@
 redis = require 'redis'
 Backbone = require 'backbone'
-{VIE} = require "#{__dirname}/../extension/palsu/static/deps/vie-2.0.0.js"

 isEmpty = (object) ->
-    for key of object
-        return false
-    return true
+  for key of object
+    return false
+  return true

-redisClient = redis.createClient()
+toUUID = ->
+  S4 = -> ((1 + Math.random()) * 0x10000|0).toString(16).substring 1
+  "#{S4()}#{S4()}-#{S4()}-#{S4()}-#{S4()}-#{S4()}#{S4()}#{S4()}"

-Backbone.sync = (method, model, options) ->
-    toUUID = () ->
-        S4 = () -> ((1 + Math.random()) * 0x10000|0).toString(16).substring 1
-        "#{S4()}#{S4()}-#{S4()}-#{S4()}-#{S4()}-#{S4()}#{S4()}#{S4()}"
+exports.createClient = (vie, config) ->
+  redisClient = redis.createClient()

+  Backbone.sync = (method, model, options) ->
+    method = 'update' if method is 'create'
     if method is 'update'
-        console.log "Update entity: ", model.id
-        if model.id.substr(0, 7) is "_:bnode"
-            # Generate UUID as the URI of the object
-            model.id = "urn:uuid:#{toUUID()}"
-            console.log "Anonymous entity, saving with URI #{model.id}"
+      console.log "Update entity: ", model.getSubjectUri()
+      if model.isNew()
+        # Generate UUID as the URI of the object
+        model['@subject'] = model.id = "urn:uuid:#{toUUID()}"
+        console.log "Anonymous entity, saving with URI #{model.getSubjectUri()}"

-        console.log "backbone update: ", model.toJSONLD()
-        for predicate, object of model.toJSONLD()
-            if predicate is "@"
-                continue
-            if VIE.RDFa._isReference(object)
-                if typeof object is "string"
-                    object = [object]
-                for reference in object
-                    reference = VIE.RDFa._fromReference reference
-                    console.log "Adding reference #{predicate}-#{reference} for #{model.id}"
-                    redisClient.sadd "#{predicate}-#{reference}", model.id
+      console.log "backbone update: ", model.toJSONLD()
+      for predicate, object of model.toJSONLD()
+        continue if predicate is '@subject'
+        predicate = model.fromReference predicate
+        if model.isReference object
+          if typeof object is 'string'
+            object = [object]
+          for reference in object
+            reference = model.fromReference reference
+            console.log "Adding reference #{predicate}-#{reference} for #{model.getSubjectUri()}"
+            redisClient.sadd "#{predicate}-#{reference}", model.getSubjectUri()

-            redisClient.hset model.id, predicate, JSON.stringify object
+        redisClient.hset model.getSubjectUri(), predicate, JSON.stringify object

     if method is 'read'
-        if model instanceof Backbone.Collection
-            if model.predicate and model.object
-                console.log "Retrieving #{model.predicate} connected to #{model.object}"
-                return redisClient.smembers "#{model.predicate}-#{model.object}", (err, subjects) ->
-                    if err
-                        console.log err
-                        return options.error err
-                    else if subjects
-                        if subjects.length is 0
-                            return options.error "Not found"
+      if model instanceof Backbone.Collection
+        model.bind 'add', (entity, col) ->
+          console.log "Adding #{entity.getSubjectUri()} to collection"
+        if model.predicate and model.object
+          console.log "Retrieving #{model.predicate} connected to #{model.object}"
+          return redisClient.smembers "#{model.predicate}-#{model.object}", (err, subjects) ->
+            return options.error err if err
+            subjects = subjects ? []
+            return options.error 'Not found' if subjects.length is 0
+            instances = []
+            for subject in subjects
+              itemInstance = vie.entities.addOrUpdate
+                '@subject': subject
+              itemInstance.fetch
+                success: (item) ->
+                  instances.push item
+                  if instances.length >= subjects.length
+                    options.success instances
+                error: (error) ->
+                  console.log "ERR", err
+        else
+          throw "When seeking Collections, you must provide predicate and object"

-                        instances = []
-                        for subject in subjects
-                            itemInstance = VIE.EntityManager.getByJSONLD
-                                "@": subject
-                            itemInstance.fetch
-                                success: (item) ->
-                                    instances.push item
-                                    if instances.length >= subjects.length
-                                        options.success instances
-            else
-                throw "When seeking Collections, you must provide predicate and object"
+      throw "Unknown entity, please provide ID" unless model.getSubjectUri()

-        else if model.id
-            console.log "Retrieving entity #{model.id}"
-            redisClient.hgetall model.id, (err, item) ->
-                if err
-                    console.log err
-                    options.error err
-                else if item
-                    if isEmpty item
-                        return options.error "Not found"
-                    jsonld =
-                        "@": model.id
-                    for predicate, object of item
-                        jsonld[predicate] = JSON.parse object
-                    options.success VIE.EntityManager.getByJSONLD jsonld
-        else
-            console.log model
-            throw "Unknown entity, please provide ID"
+      console.log "Retrieving entity #{model.getSubjectUri()}"
+      redisClient.hgetall model.getSubjectUri(), (err, item) ->
+        return options.error err if err
+        item = item ? {}
+        return options.error "Not found" if isEmpty item
+
+        jsonld =
+          '@subject': model.getSubjectUri()
+        for predicate, object of item
+          jsonld[predicate] = JSON.parse object
+        options.success vie.entities.addOrUpdate jsonld,
+          overrideAttributes: true


Added TODO.

Added TODO.

--- server/manage.coffee
+++ server/manage.coffee
@@ -302,6 +302,8 @@
       pageImageCallback = (pageNumber, canvasElement) ->
         thumbnailCanvas = new PDFJS.canvas 95, 125
         thumbnailContext = thumbnailCanvas.getContext '2d'
+
+        # TODO: Do better image resizing, antialias doesn't really help
         thumbnailContext.antialias = 'subpixel'

         thumbnailContext.drawImage canvasElement, 0, 0, canvasElement.width, canvasElement.height, 0, 0, thumbnailCanvas.width, thumbnailCanvas.height


Add cache control headers

Add cache control headers

--- server/server.coffee
+++ server/server.coffee
@@ -23,7 +23,7 @@
 app = express()

 app.use compression()
-app.use(express.static(path.resolve("./dist")))
+app.use express.static(path.resolve("./dist"),  maxAge: 30)
 app.use session
   secret: process.env.SECRET_KEY or 'CHANGE_THIS'
   saveUninitialized: true


fix the bower package inheritance

fix the bower package inheritance

--- library/membrane/bower.coffee
+++ library/membrane/bower.coffee
@@ -38,6 +38,7 @@
 http = require "http"
 util = require "util"

+{EOL} = require "os"
 {Screenplay} = require "./visual"

 # This abstract base class provides the dynamic Bower support for
@@ -84,6 +85,26 @@
         assert not _.isEmpty(sink), "got empty sink"
         return @$bowerSink = sink.toString()

+    # This is the composition hook that gets invoked when compound
+    # is being composed into other services and components. Checks
+    # if there are Bower dependencies defined on both components,
+    # and if they are - merges them together and assigns a merged
+    # dependency list to the compoun that mixed in bower support.
+    @composition: (destination) ->
+        assert _.isObject b = BowerSupport
+        assert currents = @bowerings or []
+        assert from = @identify().underline
+        return unless destination.derives b
+        into = destination.identify().underline
+        message = "Merging the Bower libs from %s"
+        previous = destination.bowerings or []
+        assert previous? and _.isArray previous
+        assert merged = previous.concat currents
+        assert _.isArray merged = _.unique merged
+        logger.debug message.blue, into, from
+        assert destination.bowerings = merged
+        try super catch error; return this
+
     # A hook that will be called prior to registering the service
     # implementation. Please refer to this prototype signature for
     # information on the parameters it accepts. Beware, this hook
@@ -114,7 +135,7 @@
         bowerings = @constructor.bowerings ?= []
         installer = install targets, {}, options
         installer.on "error", (error) -> do (error) ->
-            assert stringified = "#{error.message}"
+            assert stringified = "#{error.message}#{EOL}"
             reason = "failed Bower package installation"
             logger.error stringified.red, error.stack
             kernel.shutdownKernel reason.toString()


minor changes

minor changes

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -272,7 +272,7 @@
         log = (m, s) -> logger.info m, k(s), c(s), l(s)
         match = (s) -> "#{s.role}@#{s.version}" is identica
         assert @spserver = seaport.createServer opts or {}
-        assert _.isObject @domain; @domain.add @spserver
+        assert _.isObject @domain; try @domain.add @spserver
         logger.info create.magenta, l(host: host, port: port)
         @spserver.on "register", (s) -> log r, s if match s
         @spserver.on "free", (s) -> log f, s if match s


Minor coding refactors

Minor coding refactors

--- library/nucleus/zombie.coffee
+++ library/nucleus/zombie.coffee
@@ -83,8 +83,8 @@
         assert @instance = new this arguments...
         assert instantiated() is yes, internal
         assert upstream = @instance.upstreamAsync
-        upstream = upstream.bind @instance
-        singleton = upstream "singleton", ->
+        assert upstream = upstream.bind @instance
+        assert singleton = upstream "singleton", ->
             callback? @instance, kernel
         singleton kernel, @instance; @instance

@@ -99,7 +99,7 @@
         constructor = service.constructor or ->
         constructor.apply service, arguments
         assert upstream = service.upstreamAsync
-        upstream = upstream.bind service
-        instance = upstream "instance", ->
+        assert upstream = upstream.bind service
+        assert instance = upstream "instance", ->
             callback? service, kernel
         instance kernel, service; service


Fix case where originalZIndex wasn't set fast enough. This doesn't allow anything else to change the z-index, but I think that's unlikely

--- plugins/overlay.coffee
+++ plugins/overlay.coffee
@@ -51,7 +51,8 @@

             @options.originalBgColor = @options.currentEditable.css "background-color"
             @options.currentEditable.css 'background-color', @_findBackgroundColor(jQuery(@options.currentEditable))
-            @options.originalZIndex = @options.currentEditable.css "z-index"
+            if not @options.originalZIndex
+                @options.originalZIndex = @options.currentEditable.css "z-index"
             @options.currentEditable.css 'z-index', '350'

         hideOverlay: ->


Login: disable setting cookie

Login: disable setting cookie

--- client/Main/main.coffee
+++ client/Main/main.coffee
@@ -17,7 +17,9 @@
     mainController.accountChanged account, firstLoad
     firstLoad = no

-  status.on 'sessionTokenChanged', (token)-> $.cookie 'clientId', token
+  status.on 'sessionTokenChanged', (token)->
+    # this is disabled for now to test user log-out problem.
+    # $.cookie 'clientId', token

   status.on 'connected', ->
     destroyCurrentModal()


Track using TAGP for artwork-artwork similarity to snowplow

--- apps/artwork/client/analytics.coffee
+++ apps/artwork/client/analytics.coffee
@@ -27,3 +27,5 @@
   $document.on 'click', '.artwork-auction-results-button', ->
     track.click "Viewed 'Comparables'"

+  $document.on 'click', '#layered-search-results .artwork-item', ->
+    trackSnowplow 'genome', 'click_related_artwork', artwork.get('_id'), 'artwork'


"Workaround for weird WTF bug with RegExp objects working sometimes but not always..."

--- assets/js/models/edit_session.coffee
+++ assets/js/models/edit_session.coffee
@@ -29,11 +29,11 @@
   ["xml", "XML", ["xml"]]
 ]

-modes = _.map mappings, ([name, title, extensions]) ->
+modes = _.map mappings, ([name, title, extensions, dependencies]) ->
   name: name
   title: title
-  regex: new RegExp("^.*\\.(" + extensions.join("|") + ")$", "g")
-
+  regex: new RegExp("\\.(" + extensions.join("|") + ")$", "gi")
+  dependencies: dependencies or []

 ((exports) ->

@@ -52,20 +52,25 @@
       @setMode()

       @session.on "change", -> self.set "content", self.session.getValue()
+
+    loadMode: (mode, cb) ->
+      $script("/js/ace/mode-#{mode.name}.js", mode.name)
+      $script("/js/ace/mode-#{name}.js", name) for name in mode.dependencies
+
+      $script.ready [mode.name].concat(mode.dependencies), ->
+        mode.Mode ?= require("ace/mode/#{mode.name}").Mode
+        mode.mode ?= new mode.Mode
+
+        cb mode.mode

     setMode: ->
       self = @
-
-      if filename = @get("filename")
-        _.each modes, (mode) ->
-          if mode.regex.test(filename)
-            $script "/js/ace/mode-#{mode.name}.js", mode.name
-            $script.ready mode.name, ->
-              mode.Mode ?= require("ace/mode/#{mode.name}").Mode
-              mode.mode ?= new mode.Mode
-              self.session.setMode mode.mode
-
-
+
+      filename = @get("filename")
+      mode = _.find modes, (mode) -> filename.match(mode.regex)
+
+      if filename and mode
+        self.loadMode mode, (inst) -> self.session.setMode(inst)

   class exports.BufferCollection extends Backbone.Collection
     model: exports.Buffer


Trying to fix raspberry-pi-temp.coffee

Trying to fix raspberry-pi-temp.coffee

--- scripts/raspberry-pi-temp.coffee
+++ scripts/raspberry-pi-temp.coffee
@@ -13,24 +13,20 @@
 #   Released under the Apache v2 license
 #   https://github.com/snowbit/snowbit/blob/master/LICENSE

+# exec = require("child_process").exec
+# cmd = undefined
+# cmd = "dir \"command\""
+# command = ->
+#   exec cmd,
+#     timeout: 1000
+#   , (error, stdout, stderr) ->
+#    console.log "stdout: " + (stdout or "none")
+#     console.log "stderr: " + (stderr or "none")
+#     console.log "exec error: " + error  if error isnt null
+#     return
+#
+# command()

-# var wsh = new ActiveXObject("WScript.Shell");
-#
-# function withExecCommand(cmd, func) {
-# 	var exec = wsh.exec(cmd);
-# 	while (!exec.StdOut.AtEndOfStream) {
-# 		func(exec.StdOut.ReadLine());
-# 	}
-# 	return exec.ExitCode;
-# }
-#
-# function execCommand(cmd) {
-# 	return withExecCommand(cmd, function (line) { WScript.Echo(line);});
-# }
-#
-# var Temp = execCommand("cmd $ cat /sys/class/thermal/thermal_zone0/temp");
-# var TempC = (Temp/1000)
-#
 module.exports = (robot) ->
    robot.respond /RaspberryPi getTemp/i, (msg) ->
       msg.reply "現在のRaspberryPiの温度は" + "です。";


handle if descriptions are empty

handle if descriptions are empty

--- src/components/task-step/all-steps.cjsx
+++ src/components/task-step/all-steps.cjsx
@@ -110,11 +110,13 @@
     {description, title} = TaskStore.get(taskId)
     external_url = @getUrl()

+    descriptionHTML = <Markdown text={description}/> if description? and description.length > 0
+

         {title}

-      <Markdown text={description}/>
+      {descriptionHTML}

 Spacer = React.createClass


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -107,7 +107,7 @@
   ###
   Sprite.load = (url, loadedCallback) ->
     if spriteCache[url]
-      spriteCache[url]
+      return spriteCache[url]

     img = new Image()
     proxy = LoaderProxy()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -55,8 +55,8 @@
     @param {PowerCanvas} canvas Reference to the canvas to draw the sprite on
     @param {Number} x Position on the x axis to draw the sprite
     @param {Number} y Position on the y axis to draw the sprite
-    @param {Number} width
-    @param {Number} height
+    @param {Number} width Width to tile the
+    @param {Number} height
     ###
     fill: (canvas, x, y, width, height, repeat="repeat") ->
       pattern = canvas.createPattern(image, repeat)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -148,6 +148,7 @@
           Module = Module.constantize()
         else
           for key, value of root
+            if

         unless I.included_modules.include moduleName
           I.included_modules.push moduleName


core_object

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -107,27 +107,27 @@
     existing methods.

-       I =
-         x: 30
-         y: 40
-         maxSpeed: 5
+    I =
+      x: 30
+      y: 40
+      maxSpeed: 5

-       # we are using extend to give player
-       # additional methods that Core doesn't have
-       player = Core(I).extend
-         increaseSpeed: ->
-           I.maxSpeed += 1
+    # we are using extend to give player
+    # additional methods that Core doesn't have
+    player = Core(I).extend
+      increaseSpeed: ->
+        I.maxSpeed += 1

-         # this will execute before the update method
-         beforeUpdate: ->
-           checkPowerupStatus()
+      # this will execute before the update method
+      beforeUpdate: ->
+        checkPowerupStatus()

-       player.I.maxSpeed
+    player.I.maxSpeed
     => 5

-       player.increaseSpeed()
+    player.increaseSpeed()

-       player.I.maxSpeed
+    player.I.maxSpeed
     => 6


Suppression des cartouches du HUD lors de leur sortie de l'écran

--- js/game/entities/HUD.coffee
+++ js/game/entities/HUD.coffee
@@ -13,7 +13,7 @@
             @width = 3
             @height = 15

-            @weight = 300
+            @weight = 250

             @vel =
                 x: Math.random() * 100 - 50
@@ -23,7 +23,8 @@
         handleUpdate: (dt) ->
             @vel.y += @weight * dt * 9.81
             @angle += @vel.ang * dt
-            @updatePosition(dt)
+            @updatePosition(dt)
+            @parent.removeChild(@) if @isOffGameScreen()

         handleDraw: ->
             @ctx.save()


New constructor defaults to all parsers

New constructor defaults to all parsers

--- lib/written/app/assets/javascripts/written/core/content.coffee
+++ lib/written/app/assets/javascripts/written/core/content.coffee
@@ -15,7 +15,7 @@

     parsers = options.parsers
     if !parsers?
-      parsers = new Written.Parsers.all()
+      parsers = new Written.Parsers()

     @parsers = parsers


bugfix

bugfix

--- lib/psd/layer_info/typetool.coffee
+++ lib/psd/layer_info/typetool.coffee
@@ -97,7 +97,7 @@
         leading = f.Leading
       else
         leading = 'auto'
-      return leadinga
+      return leading

   sizes: ->
     return [] if not @engineData? and not @styles().FontSize?


Formatting clean-up.

Formatting clean-up.

--- src/timber.coffee
+++ src/timber.coffee
@@ -73,8 +73,7 @@
         throw new Error("No element with id: #{id}") if not @canvas

         @context = @canvas.getContext("2d")
-        if not @context
-            throw new Error("no context")
+        throw new Error("no context") if not @context

     # Render the given elements on the canvas.
     #


Don't run specs on Linux CI for now

Don't run specs on Linux CI for now

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -227,11 +227,12 @@
   grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
   grunt.registerTask('docs', ['markdown:guides', 'build-docs'])

-
   ciTasks = ['output-disk-space', 'download-atom-shell', 'build']
-  ciTasks.push('dump-symbols') unless process.platform is 'win32'
+  ciTasks.push('dump-symbols') if process.platform isnt 'win32'
   ciTasks.push('mkdeb') if process.platform is 'linux'
   ciTasks.push('set-version', 'check-licenses', 'lint', 'test', 'codesign', 'publish-build')
+  ciTasks.push('test') if process.platform isnt 'linux'
+  ciTasks.push('codesign', 'publish-build')
   grunt.registerTask('ci', ciTasks)

   defaultTasks = ['download-atom-shell', 'build', 'set-version']


added fork scrolling state and behavior.

--- src/components/app/pages/thread/fork.coffee
+++ src/components/app/pages/thread/fork.coffee
@@ -1,6 +1,40 @@
 React = require 'react'

 Fork = React.createClass
+
+  getInitialState: ->
+    return {
+      scrollFixed: true
+    }
+
+  _scrollToBottom: ->
+    node = @getDOMNode()
+    node.scrollTop = node.scrollHeight
+
+  _saveScroll: ->
+    node = @getDOMNode()
+    @scrollHeight = node.scrollHeight;
+    @scrollTop = node.scrollTop
+
+  _scrollToSaved: ->
+    node = @getDOMNode()
+    node.scrollTop = @scrollTop + (node.scrollHeight - @scrollHeight)
+
+  _onScroll: (e) ->
+    node = @getDOMNode()
+    scrollFixed = node.scrollTop == @scrollTop
+    if scrollFixed != @state.scrollFixed
+      @setState {scrollFixed}
+
+  componentDidUpdate: ->
+    # If we are in "fixed" mode, make sure we are still scrolled down
+    if @state.scrollFixed
+      @_scrollToBottom()
+    else
+      @_scrollToSaved()
+
+  componentWillUpdate: ->
+    @_saveScroll()

   # Return if we have the message
   _hasMessage: (messageId) ->
@@ -36,7 +70,7 @@
       return messages

   render: ->
-

+

       {@_renderMessages()}


adding replace_into method

adding replace_into method

--- src/toaster/utils/array-util.coffee
+++ src/toaster/utils/array-util.coffee
@@ -63,4 +63,11 @@
 		diff

 	@has:(source, search, by_property)->
-		return ArrayUtil.find( source, search, by_property )?+		return ArrayUtil.find( source, search, by_property )?
+
+	@replace_into:( src, index, items )->
+		items = [].concat items
+		src.splice index, 1
+		while items.length
+			src.splice index++, 0, items.shift()
+		src


collaboration: add handler for host end session

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -684,10 +684,8 @@
     if @amIHost
       @endCollaborationForHost
         success: =>
-          # TODO: move this into its own method.
-          @statusBar.emit 'CollaborationEnded'
-          sharedSuccessFn()
-          @cleanupCollaboration()
+          @modal?.destroy()
+          @handleCollaborationEndedForHost()

     else
       @endCollaborationForParticipant
@@ -712,6 +710,22 @@
             callbacks.success()

+  handleCollaborationEndedForHost: ->
+
+    return  unless @stateMachine.state in ['Ending']
+
+    @rtm.once 'RealtimeManagerWillDispose', =>
+      @chat.emit 'CollaborationEnded'
+      @chat.destroy()
+      @chat = null
+      @statusBar.emit 'CollaborationEnded'
+
+    @rtm.once 'RealtimeManagerDidDispose', =>
+      kd.utils.defer @bound 'prepareCollaboration'
+
+    @cleanupCollaboration()
+
+
   endCollaborationForParticipant: (callbacks) ->

     @broadcastMessage { type: 'ParticipantWantsToLeave' }
@@ -791,7 +805,6 @@
     @rtm.once 'RealtimeManagerDidDispose', =>
       @rtm = null
       delete @stateMachine
-      @prepareCollaboration  if @amIHost

     @rtm.dispose()


collaboration: emit an event for cleanup

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -818,6 +818,7 @@
       delete @stateMachine

     @rtm.dispose()
+    @emit 'CollaborationDidCleanup'

   # environment related


bugfix

bugfix

--- server/controllers/routes.coffee
+++ server/controllers/routes.coffee
@@ -31,7 +31,7 @@
                 return cb err if err
                 fs.unlink 'search-index.json', (err) ->
                     #return cb err if err # might not exist
-                    RDFStorage.requestDestroy 'all', (err) ->
+                    RDFStorage.requestDestroy 'semsearchall', (err) ->
                         return cb err if err
                         Progress.requestDestroy 'byDoctype', (err) ->
                             return cb err if err


improve status

improve status

--- server/lib/status_checker.coffee
+++ server/lib/status_checker.coffee
@@ -48,7 +48,7 @@

         async.series [
             @getChecker "couchdb", couchUrl
-            @getChecker "controller", controllerUrl, "drones/running"
+            @getChecker "controller", controllerUrl
             @getChecker "datasystem", dataSystemUrl
             @getChecker "indexer", indexerUrl
             @getChecker "home", homeUrl


Form popover: Merge opts of pane.

Form popover: Merge opts of pane.

--- src/elements/FormPopover/FormPopover.coffee
+++ src/elements/FormPopover/FormPopover.coffee
@@ -190,13 +190,11 @@
 		if not pop_opts.backdrop.policy
 			pop_opts.backdrop.policy = "click"

-		# pop_opts.element = @__button
 		if not pop_opts.pane
 			pop_opts.pane = {}

-		pop_opts.pane.padded = true
-
 		CUI.util.assert(CUI.util.isPlainObject(pop_opts.pane), "new CUI.FormPopover", "opts.pane must be PlainObject", opts: pop_opts)
+		CUI.util.mergeMap(pop_opts.pane, padded: true)

 		if CUI.util.isEmpty(pop_opts.class)
 			pop_opts.class = ""


index: Default function for each_K is now the identity function.

--- client/index.coffee
+++ client/index.coffee
@@ -50,7 +50,7 @@
     for k of extractors
       do (k) =>
         @_.indices[k] = {}
-        this["each_#{k}"] = (f) ->
+        this["each_#{k}"] = (f = (x) -> x) ->
           f(v) for v of @_.indices[k]
         this["with_#{k}"] = (value) ->
           Heron.Util.keys(@_.indices[k][value])


Fix bug where username incorrectly passed to findOneByUsername.  Also adds arugment validation.

--- server/methods/addUserToRoom.coffee
+++ server/methods/addUserToRoom.coffee
@@ -1,7 +1,13 @@
 Meteor.methods
 	addUserToRoom: (data) ->
 		fromId = Meteor.userId()
-		# console.log '[methods] addUserToRoom -> '.green, 'fromId:', fromId, 'data:', data
+		console.log '[methods] addUserToRoom -> '.green, 'data:', data
+
+		unless Match.test data?.rid, String
+			throw new Meteor.Error 'invalid-rid'
+
+		unless Match.test data?.username, String
+			throw new Meteor.Error 'invalid-username'

 		room = RocketChat.models.Rooms.findOneById data.rid

@@ -13,7 +19,7 @@
 		if room.usernames.indexOf(data.username) isnt -1
 			return

-		newUser = RocketChat.models.Users.findOneByUsername username: data.username
+		newUser = RocketChat.models.Users.findOneByUsername data.username

 		RocketChat.models.Rooms.addUsernameById data.rid, data.username


Remove config from replContext

Remove config from replContext

--- server/packages/config/index.coffee
+++ server/packages/config/index.coffee
@@ -78,8 +78,3 @@
 			).catch next

 	]
-
-# ## Implements hook `replContext`
-#
-# } `TODO`: Should probably just remove this and `require 'nconf'`.
-exports.$replContext = (context) -> context.config = nconf


Change "change" bindings to "changed" in order to avoid jquery change overridding.

--- binds.coffee
+++ binds.coffee
@@ -90,7 +90,7 @@
         $(this).attr "selected", this.value in newValue
     else if p.element.val() isnt newValue
       p.element.val newValue
-    p.element.trigger 'change'
+    p.element.trigger 'changed'

     return if c.firstRun
     if isInput and not VmHelper.delayed[delayName + "X"]
@@ -182,7 +182,7 @@
         p.element.prop 'checked', val if p.element.is(':checked') isnt val
       else
         p.element.prop 'checked', val is p.element.val() if p.element.is(':checked') isnt  (val is p.element.val())
-  p.element.bind 'change', ->
+  p.element.bind 'changed', ->
     val = getProperty(p.vm, p.property)
     if VmHelper.isArray val
       if p.element.is(':checked')
@@ -193,15 +193,15 @@
       newValue = if p.element.attr('type') is 'checkbox' then p.element.is(':checked') else p.element.val()
       setProperty(p.vm, p.property, newValue) if val isnt newValue

-ViewModel.addBind 'change', (p) ->
+ViewModel.addBind 'changed', (p) ->
   propToFunc = {}
-  if _.isObject(p.elementBind.change)
-    propToFunc = p.elementBind.change
+  if _.isObject(p.elementBind.changed)
+    propToFunc = p.elementBind.changed
   else
     b = p.elementBind
     propWithValue = b.value or b.checked or b.text or b.focused or b.hover
     if propWithValue
-      propToFunc[propWithValue] = p.elementBind.change
+      propToFunc[propWithValue] = p.elementBind.changed

   if _.size(propToFunc)
     for prop of propToFunc
@@ -214,12 +214,12 @@
     ViewModel.binds.default(p)

 ViewModel.addBind 'file', (p) ->
-  p.element.bind 'change', (e) ->
+  p.element.bind 'changed', (e) ->
     file = if e.target.files?.length then e.target.files[0] else null
     setProperty(p.vm, p.property,  file)

 ViewModel.addBind 'files', (p) ->
-  p.element.bind 'change', (e) ->
+  p.element.bind 'changed', (e) ->
     prop = getProperty(p.vm, p.property)
     prop.clear()
     prop.push(f) for f in e.target.files


custom add slot support

custom add slot support

add slot (oblong hole) support for custom pattern generation.
these are often used for mounting tabs of shells and power jacks.
used slotWidth and slotHeight in the pattern to define a slot.
the slot hole shape is up to the generator but rounded caps are
the industry standard.
circle shape indicate to use slot pads with rounded caps.

--- src/pattern/default/custom.coffee
+++ src/pattern/default/custom.coffee
@@ -13,10 +13,36 @@
   pins = element.pins
   if housing['numbers' + suffix]? then housing['numbers' + suffix] = element.parsePinNumbers housing['numbers' + suffix]
   numbers = housing['numbers' + suffix] ? Object.keys pins
-  unless (housing['holeDiameter' + suffix]?) or (housing['padDiameter' + suffix]?) or (housing['padWidth' + suffix]? and housing['padHeight' + suffix]?)
+  unless (housing['holeDiameter' + suffix]?) or (housing['padDiameter' + suffix]?) or (housing['padWidth' + suffix]? and housing['padHeight' + suffix]?) or (housing['slotWidth' + suffix]? and housing['slotHeight' + suffix]?)
     return false
   hasPads = false
-  if housing['holeDiameter' + suffix]?
+  if housing['slotWidth' + suffix]? and housing['slotHeight' + suffix]?
+    slotWidth = housing['slotWidth' + suffix]
+    slotHeight = housing['slotHeight' + suffix]
+    holeDiameter = if slotWidth > slotHeight then slotWidth else slotHeight
+    if slotWidth > slotHeight
+      padHeight = housing['padHeight' + suffix] ? calculator.padDiameter pattern, housing, slotHeight
+      padWidth = housing['padWidth' + suffix] ? slotWidth + (padHeight - slotHeight)
+      padDiameter = housing['padDiameter' + suffix] ? holeDiameter + (padHeight - slotHeight)
+    else
+      padWidth = housing['padWidth' + suffix] ? calculator.padDiameter pattern, housing, slotWidth
+      padHeight = housing['padHeight' + suffix] ? slotHeight + (padWidth - slotWidth)
+      padDiameter = housing['padDiameter' + suffix] ? holeDiameter + (padWidth - slotWidth)
+    pad =
+      type: 'through-hole'
+      slotWidth: slotWidth
+      slotHeight: slotHeight
+      width: padWidth
+      height: padHeight
+      shape: if (pinNumber is 0) and housing.polarized then 'rectangle' else 'circle'
+      layer: ['topCopper', 'topMask', 'intCopper', 'bottomCopper', 'bottomMask']
+    if (padWidth < slotWidth) and (padHeight < slotHeight)
+      pad.type = 'mounting-hole'
+      pad.layer = ['topCopper', 'topMask', 'intCopper', 'bottomCopper', 'bottomMask']
+      pad.width = slotWidth
+      pad.height = slotHeight
+      pad.shape = 'circle'
+  else if housing['holeDiameter' + suffix]?
     holeDiameter = housing['holeDiameter' + suffix]
     padDiameter = housing['padDiameter' + suffix] ? calculator.padDiameter pattern, housing, holeDiameter
     padWidth = housing['padWidth' + suffix] ? padDiameter
@@ -34,7 +60,7 @@
       pad.width = holeDiameter
       pad.height = holeDiameter
       pad.shape = 'circle'
-  else
+  else if (housing['padDiameter' + suffix]?) or (housing['padWidth' + suffix]? and housing['padHeight' + suffix]?)
     padDiameter = housing['padDiameter' + suffix]
     padWidth = housing['padWidth' + suffix] ? padDiameter
     padHeight = housing['padHeight' + suffix] ? padDiameter


Optimization

Optimization

--- src/tumblr.coffee
+++ src/tumblr.coffee
@@ -47,7 +47,7 @@
     params.join ''

   request = (url, fn = ->) ->
-    xhr uri: url, (error, request, body) ->
+    xhr {url}, (error, request, body) ->
       body = JSON.parse body
       throw new Error body.meta.msg if body.meta.status isnt 200
       fn.call body, body.response