one way to denormalize

one way to denormalize

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -195,7 +195,10 @@
       return callback err if err = err or res.body.error
       post = res.body
       # Ensure the article is linked to the Gravity post
-      @save _.extend(article, { gravity_id: post._id, slug: post.id }), (err, article) ->
+      @save _.extend(article, {
+        gravity_id: post._id
+        slug: post.id
+      }), (err, article) ->
         return callback err if err
         # Delete any existing attachments/artworks
         async.parallel [


Remove unneeded apply

Remove unneeded apply

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -514,8 +514,6 @@

 			options.rootDocOverride_id = getRootDocOverride_id()

-			$scope.$apply()
-
 			sendCompileRequest(options)
 				.then (response) ->
 					{ data } = response


Install beta atom shell script as /bin/atom-beta on linux

--- build/tasks/install-task.coffee
+++ build/tasks/install-task.coffee
@@ -30,11 +30,11 @@
       fs.renameSync(tempFolder, installDir)
     else
       binDir = path.join(installDir, 'bin')
-      appDirName = appName.toLowerCase().replace(/\s+/g, '-')
-      shareDir = path.join(installDir, 'share', appDirName)
+      appFileName = appName.toLowerCase().replace(/\s+/g, '-')
+      shareDir = path.join(installDir, 'share', appFileName)

       mkdir binDir
-      cp 'atom.sh', path.join(binDir, 'atom')
+      cp 'atom.sh', path.join(binDir, appFileName)
       rm shareDir
       mkdir path.dirname(shareDir)
       cp shellAppDir, shareDir
@@ -43,7 +43,7 @@
       tmpDir = if process.env.TMPDIR? then process.env.TMPDIR else '/tmp'
       if installDir.indexOf(tmpDir) isnt 0
         desktopFile = path.join('resources', 'linux', 'atom.desktop.in')
-        desktopInstallFile = path.join(installDir, 'share', 'applications', appDirName + '.desktop')
+        desktopInstallFile = path.join(installDir, 'share', 'applications', appFileName + '.desktop')

         {description} = grunt.file.readJSON('package.json')
         iconName = path.join(shareDir, 'resources', 'app.asar.unpacked', 'resources', 'atom.png')
@@ -56,6 +56,6 @@
       # Create relative symbol link for apm.
       process.chdir(binDir)
       rm('apm')
-      fs.symlinkSync(path.join('..', 'share', appDirName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), 'apm')
+      fs.symlinkSync(path.join('..', 'share', appFileName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), 'apm')

       fs.chmodSync(path.join(shareDir, 'atom'), "755")


Very slight re-factoring

Very slight re-factoring

--- packages/emberella/lib/views/image_view.coffee
+++ packages/emberella/lib/views/image_view.coffee
@@ -90,7 +90,7 @@
     view = @

     didImageLoad = (e) ->
-      $img = jQuery(this)
+      $img = jQuery(@)
       $img.off('load.image-view', didImageLoad)

       #Do nothing if view instance is destroyed
@@ -111,8 +111,7 @@
     @chainable
   ###
   updateSrc: ->
-    view = @
-    $img = view.$()
+    $img = @$()
     src = get(@, 'src')
     didImageLoad = get @, 'didImageLoad'

@@ -122,7 +121,7 @@
     if jQuery.trim(src) is ''
       return @

-    set view, 'loading', true #enter loading state
+    set @, 'loading', true #enter loading state

     $img.on('load.image-view', didImageLoad)
     $img.attr('src', src)
@@ -172,7 +171,7 @@
     @event willDestroyElement
   ###
   willDestroyElement: ->
-    $img = this.$()
+    $img = @$()
     didImageLoad = get @, 'didImageLoad'
     $img.off('load.image-view', didImageLoad)
     @_super()


FIX: Amend incorrect template path for user settings modal

--- src/app/users/modals/user-settings-modal/user-settings-modal.coffee
+++ src/app/users/modals/user-settings-modal/user-settings-modal.coffee
@@ -5,24 +5,23 @@

   UserSettingsModal.show = (user) ->
     $modal.open
-      templateUrl: 'users/modals/user-settings-modal/user-modal-context.tpl.html'
+      templateUrl: 'users/modals/user-settings-modal/user-settings-modal.tpl.html'
       controller: 'UserSettingsModalCtrl'
       resolve:
-        user: ->  $scope.currentUser
+        user: -> user

   UserSettingsModal
 )

 .controller('UserSettingsModalCtrl', ($scope, $modalInstance, alertService, analyticsService, currentUser, User, user, auth) ->
   $scope.user = user
-  $scope.isNew = user.id is undefined
+  $scope.isNew = user?.id is undefined

   if $scope.isNew
     $scope.users = User.query()

   $scope.currentUser = currentUser

-  $scope.isNew = isNew
   $scope.modalState = {}

   createNewUser = ->


Account: hide koding keys and credentials

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -18,9 +18,9 @@
       title : "Develop"
       items : [
         { slug : 'SSH',         title : "SSH keys",           listHeader: "Your SSH Keys",          listType: "keys" }
-        { slug : 'Keys',        title : "Koding Keys",        listHeader: "Your Koding Keys",       listType: "kodingKeys" }
+        # { slug : 'Keys',        title : "Koding Keys",        listHeader: "Your Koding Keys",       listType: "kodingKeys" }
         { slug : 'Referral',    title : "Referral System",    listHeader: "Your Referral Options",  listType: "referralSystem" }
-        { slug : 'Credentials', title : "Credentials",        listHeader: "Your Credentials",       listType: "credentials" }
+        # { slug : 'Credentials', title : "Credentials",        listHeader: "Your Credentials",       listType: "credentials" }
       ]
     danger  :
       title : "Danger"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -124,7 +124,7 @@
       hsl = @toHsl()
       hsl[0] = (hsl[0] + degrees) % 360

-      return Color(hslToRgb(hsl))
+      return Color2(hslToRgb(hsl))

     toHex: ->
       padString = (hexString) ->


Somewhat working format on save

Somewhat working format on save

--- lib/format.coffee
+++ lib/format.coffee
@@ -22,14 +22,15 @@
   activate: (state) ->
     atom.workspaceView.command "jsformat:format", => @format(state)

+    if atom.config.get('jsformat.format_on_save') ? @configDefaults['format_on_save']
+      # probably shouldn't be assuming .getActivePaneItem always returns an editor
+      @subscribeToEvents(atom.workspace.getActivePaneItem())
+
   format: (state) ->
     editor = atom.workspace.activePaneItem

     if !editor
       return
-
-    if atom.config.get('jsformat.format_on_save') ? @configDefaults['format_on_save']
-      @subscribeToEvents(editor)

     grammar = editor.getGrammar()?.scopeName

@@ -66,13 +67,29 @@

   subscribeToEvents: (editor) ->
     buffer = editor.getBuffer()
-    bufferSavedSubscription = @subscribe buffer, 'on-will-save', =>
-      buffer.transact =>
-        @formatJavascript(editor)
+    grammar = editor.getGrammar()?.scopeName

-    @subscribe editor, 'destroyed', =>
-      bufferSavedSubscription.off()
-      @unsubscribe(editor)
+    if grammar is 'source.json' or grammar is 'source.js'
+      # event is only attached once user has called JSFormat once,
+      # figure out how to set this up at the beginning on any supported file
+      #
+      # figure out how to destroy the subscription, this variable
+      # seems to be inaccessible once the event has been subscribed
+      subscription = buffer.onWillSave ->
+        buffer.transact ->
+          # figure out how to not have to call module.exports
+          module.exports.formatJavascript editor
+    else
+      notification = new FileTypeNotSupportedView(state)
+      atom.workspaceView.append(notification)
+      destroyer = () ->
+        notification.detach()

-    @subscribe buffer, 'destroyed', =>
-      @unsubscribe(buffer)
+      setTimeout destroyer, 1500
+
+    # @subscribe editor, 'destroyed', =>
+    #   bufferSavedSubscription.off()
+    #   @unsubscribe(editor)
+    #
+    # @subscribe buffer, 'destroyed', =>
+    #   @unsubscribe(buffer)


Fix #69

Fix #69

--- lib/format.coffee
+++ lib/format.coffee
@@ -150,6 +150,7 @@
         # @editorCloseSubscriptions = new Observer()

   displayUnsupportedLanguageNotification: (language) ->
+    editor = atom.workspace.getActiveTextEditor()
     title = editor.getTitle()
     ext = path.extname title
     message = ext.length > 0 ? ext : title


stacks: add handleIdenfier func to handle route for VM page

--- client/home/lib/stacks/index.coffee
+++ client/home/lib/stacks/index.coffee
@@ -16,7 +16,7 @@
 EnvironmentFlux = require 'app/flux/environment'

 AddManagedMachineModal = require 'app/providers/managed/addmanagedmachinemodal'
-
+VirtualMachinesSelectedMachineFlux = require 'home/virtualmachines/flux/selectedmachine'

 module.exports = class HomeStacks extends kd.CustomScrollView

@@ -58,10 +58,17 @@

     for pane in @tabView.panes when kd.utils.slugify(pane.name) is action
       pane_ = @tabView.showPane pane
+      if action is 'virtual-machines'
+        VirtualMachinesSelectedMachineFlux.actions.setSelectedMachine null
       break

+  handleIdentifier: (identifier, action) ->

-
+    for pane in @tabView.panes when kd.utils.slugify(pane.name) is action
+      pane_ = @tabView.showPane pane
+      if action is 'virtual-machines'
+        VirtualMachinesSelectedMachineFlux.actions.setSelectedMachine identifier
+      break

   createStacksViews: ->


Client:Login: create a function that adds custom data into form with given type and data

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -465,6 +465,11 @@
   #           break
   #       router.clear()  unless routed

+  setCustomDataToForm: (type, data)->
+    formName = "#{type}Form"
+    @[formName].addCustomData data
+    # @resetForm.addCustomData {recoveryToken}
+
   animateToForm: (name)->

     @show =>


Client:Login: remove banner from password recovry

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -277,7 +277,7 @@
           duration  : 4500

   resendEmailConfirmationToken:(formData)->
-    KD.remote.api.JEmailConfirmation.resetToken formData['username-or-email'], (err)=>
+    KD.remote.api.JPasswordRecovery.recoverPassword formData['username-or-email'], (err)=>
       @resendForm.button.hideLoader()
       if err
         new KDNotificationView
@@ -420,14 +420,6 @@

       $('#group-landing').css 'height', '100%'
       $('#group-landing').css 'opacity', 1
-
-  headBannerShowRecovery:(recoveryToken)->
-
-    @showHeadBanner "Hi, seems like you came here to reclaim your account. Click here when you're ready!", =>
-      KD.getSingleton('router').clear '/Recover/Password'
-      @headBanner.updatePartial "You can now create a new password for your account"
-      @resetForm.addCustomData {recoveryToken}
-      @animateToForm "reset"

   headBannerShowInvitation:(invite)->


Add static fetch method for creating and fetching

--- client/lanes/data/Collection.coffee
+++ client/lanes/data/Collection.coffee
@@ -31,13 +31,18 @@
         @errors=[]
         Lanes.Vendor.Ampersand.Collection.apply(this, arguments)

-    isLoaded:->
-        @isLoaded
-
+    @fetch: (options)->
+        collection = new this
+        collection.fetch(options)
+        collection
+
     fetch: (options={})->
         @isLoaded = true
         wrapRequest(this,options)
         super(options).then => @
+
+    isLoaded:->
+        @isLoaded

     ensureLoaded: ( callback )->
         if ! @isLoaded && ! this.length
@@ -46,6 +51,8 @@
             callback()

     isDirty: ->
+        for model in @models
+            return true if model.isDirty()
         false

     parse:(resp)->
@@ -59,8 +66,16 @@
     url: ->
         @model::urlRoot()

-    sync: ->
-        Lanes.Data.Sync.apply(this,arguments)
+    sync: Lanes.Data.Sync
+
+    save: (options)->
+        Lanes.Data.Sync('update', this, options)
+
+    dataForSave: (options)->
+        unsaved = []
+        for model in @models
+            unsaved.push( model.unsavedData() ) if model.isDirty()
+        unsaved

     _prepareModel: (attrs, options={})->
         options.collection = this;
@@ -110,6 +125,9 @@
         CommonMethods
     ]

+    url: ->
+        this.collection.url()
+
     filter: ->
         this._runFilters()


report change as percentage

report change as percentage

--- client/plugins/method/method.coffee
+++ client/plugins/method/method.coffee
@@ -94,8 +94,9 @@
             [value, list, hover] = [apply(args[1], list), [], "#{args[1]} of #{list.length} numbers"]
             line = args[2]
             if output[line]? or input[line]?
-              if value != (previous = asValue(output[line]||input[line]))
-                comment = "previously #{previous} Δ#{value-previous}"
+              previous = asValue(output[line]||input[line])
+              if Math.abs(change = value/previous-1) > 0.0001
+                comment = "previously #{previous}\nΔ #{round(change*100)}%"
             output[line] = value
           else if args = line.match /^([A-Z]+)$/
             [value, list, hover] = [apply(args[1], list), [], "#{args[1]} of #{list.length} numbers"]


fix sorting mentionables

fix sorting mentionables

--- client/shared/helpers/listen.coffee
+++ client/shared/helpers/listen.coffee
@@ -16,7 +16,7 @@
         u.name.toLowerCase().startsWith($scope.q) or
         u.username.toLowerCase().startsWith($scope.q) or
         u.name.toLowerCase().includes(" #{$scope.q}")
-      $scope.mentionables = _.sortBy(chain.data(), [(u) -> 0 - Records.events.find(actorId: u.id).length])
+      $scope.mentionables = _.sortBy(chain.data(), (u) -> (0 - Records.events.find(actorId: u.id).length))

     fetchThenUpdate = _.throttle ->
       Records.users.fetchMentionable($scope.q, model).then (response) ->


add gopath to atom go-plus

add gopath to atom go-plus

--- atom/config.cson
+++ atom/config.cson
@@ -28,4 +28,5 @@
   linter: {}
   "atom-beautify":
     _analyticsUserId: "a6a0720b-ef9b-4635-a6bf-67af0c531216"
-  "go-plus": {}
+  "go-plus":
+    goPath: "/home/edgard/Documents/code/go"


Atom configuration - turn off prettier autoformatting

--- atom/config.cson
+++ atom/config.cson
@@ -4,6 +4,7 @@
     disabledPackages: [
       "language-gfm"
       "markdown-preview"
+      "markdown-writer"
     ]
     openEmptyEditorOnStart: false
     projectHome: "/Users/thomasmattachione/source"
@@ -48,8 +49,7 @@
     ]
     "use-smart-mode?": true
   "prettier-atom":
-    formatOnSaveOptions:
-      enabled: true
+    formatOnSaveOptions: {}
     prettierOptions:
       singleQuote: true
   "proto-repl":


Update Atom's linter-mypy config

Update Atom's linter-mypy config

--- atom/config.cson
+++ atom/config.cson
@@ -68,6 +68,9 @@
       disableWhenNoEslintConfig: false
   "linter-flake8":
     executablePath: "/Users/jonklo/.pyenv/versions/tienda-3.7.5/bin/flake8"
+  "linter-mypy":
+    executablePath: "/Users/jonklo/.pyenv/versions/tienda-3.7.5/bin/python"
+    mypyPath: "/Users/jonklo/.pyenv/versions/tienda-3.7.5/bin/mypy"
   "linter-pylint":
     executablePath: "/Users/jonklo/.pyenv/versions/tienda-3.7.5/bin/pylint"
   "linter-stylelint":


Updata atom config

Updata atom config

--- atom/config.cson
+++ atom/config.cson
@@ -1,25 +1,28 @@
-'*':
-  'editor':
-    'fontFamily': 'PragmataPro'
-    'showInvisibles': true
-    'showIndentGuide': true
-    'invisibles': {}
-  'core':
-    'disabledPackages': [
-      'autocomplete'
-      'spell-check'
+"*":
+  editor:
+    fontFamily: "PragmataPro"
+    showInvisibles: true
+    showIndentGuide: true
+    invisibles: {}
+    autoIndentOnPaste: false
+  core:
+    disabledPackages: [
+      "autocomplete"
+      "spell-check"
     ]
-    'themes': [
-      'solarized-dark-ui'
-      'solarized-dark-syntax'
+    themes: [
+      "solarized-dark-ui"
+      "solarized-dark-syntax"
     ]
-    'projectHome': '/home/edgard/Documents/code'
-  'exception-reporting':
-    'userId': '1b98cd61-c072-c275-56fd-5715ea36d0bd'
-  'welcome':
-    'showOnStartup': false
-  'autocomplete-plus': {}
-  'minimap': {}
-  'git-diff':
-    'showIconsInEditorGutter': true
-  'spell-check': {}
+    projectHome: "/home/edgard/Documents/code"
+  "exception-reporting":
+    userId: "1b98cd61-c072-c275-56fd-5715ea36d0bd"
+  welcome:
+    showOnStartup: false
+  "autocomplete-plus": {}
+  minimap: {}
+  "git-diff":
+    showIconsInEditorGutter: true
+  "spell-check": {}
+  "atom-terminal":
+    app: "/usr/bin/gnome-terminal"


atom: clicking minimap goes to location

atom: clicking minimap goes to location

--- atom/config.cson
+++ atom/config.cson
@@ -61,7 +61,8 @@
 	"linter-php": {}
 	"linter-ui-default":
 		showPanel: false
-	minimap: {}
+	minimap:
+		moveCursorOnMinimapClick: true
 	"one-dark-ui": {}
 	"open-file-in":
 		applications: "Finder,FireFox,Google Chrome,TextEdit"


Restrict tree view key bindings to macOS

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -155,7 +155,7 @@
   'alt-cmd-b': 'project-find:toggle-whole-word-option'

 # Tree View sidebar (when focused)
-'.tree-view':
+'.platform-darwin .tree-view':

   # Rename the selected file or directory
   'e': 'tree-view:rename'


fix casperjs test

fix casperjs test

--- caspertests/tests/iosapp.coffee
+++ caspertests/tests/iosapp.coffee
@@ -7,33 +7,33 @@

   casper.thenOpen settings.baseURL() + "/admin/api/v1/iosapp", ->
     test.assertHttpStatus 200
-  #   jsonData = JSON.parse(@getPageContent())
-  #   test.assertEqual jsonData.items[0].title, "title1", "title of the first entity should be title1"
-  #   test.assertEqual jsonData.items[1].title, "title2", "title of the first entity should be title2"
-

   # add entity by post
   casper.thenOpen settings.baseURL() + "/admin/api/v1/iosapp",
     method: "post"
-    data:{
-      app_id: "app_id1"
-      title: "title1"
-      webhook_url: "http://hoge.com/xxxxx"
+    data: JSON.stringify({
+      app_id:"app_id1"
+      title:"title1"
+      webhook_url: "http://xxx.xxx/xxxx"
       content: "foobar"
-    }
+    })
+    headers:
+      "Content-Type": "application/json; charset=utf-8"
   , ->
     @echo "POST request has been sent."
     test.assertHttpStatus 200
     @echo @getPageContent()

-  # add entity by post
   casper.thenOpen settings.baseURL() + "/admin/api/v1/iosapp",
     method: "post"
-    data:
-      app_id: "app_id2"
-      title: "title2"
-      webhook_url: "http://hoge.com/xxxxx"
-      content: "foobar"
+    data: JSON.stringify({
+      app_id:"app_id2"
+      title:"title2"
+      webhook_url: "http://xxx.xxx/xxxx"
+      content: "moo bar"
+    })
+    headers:
+      "Content-Type": "application/json; charset=utf-8"
   , ->
     @echo "POST request has been sent."
     test.assertHttpStatus 200


better paste support for div elements

better paste support for div elements

--- app/assets/javascripts/dante/editor.js.coffee
+++ app/assets/javascripts/dante/editor.js.coffee
@@ -36,6 +36,7 @@
     @spell_check     = opts.spellcheck || false
     @disable_title   = opts.disable_title || false
     @store_interval  = opts.store_interval || 15000
+    @paste_element_id = "#dante-paste-div"
     window.debugMode = opts.debug || false
     $(@el).addClass("debug") if window.debugMode
     if (localStorage.getItem('contenteditable'))
@@ -471,17 +472,20 @@
       cbd = ev.originalEvent.clipboardData
       pastedText = if _.isEmpty(cbd.getData('text/html')) then cbd.getData('text/plain') else cbd.getData('text/html')

-    utils.log(pastedText) # Process and handle text...
+    utils.log("Process and handle text...")
     #detect if is html
     if pastedText.match(/<\/*[a-z][^>]+?>/gi)
       utils.log("HTML DETECTED ON PASTE")
-      $(pastedText)
-
-      document.body.appendChild($("

")[0])
-      $("#paste").html(pastedText)
-      @setupElementsClasses $("#paste"), ()=>
-        nodes = $($("#paste").html()).insertAfter($(@aa))
-        $("#paste").remove()
+      pastedText = pastedText.replace(/&.*;/g, "")
+      #convert pasted divs in p before copy contents into div
+      pastedText = pastedText.replace(/
([\w\W]*?)<\/div>/gi, '
$1
')
+
+      document.body.appendChild($("

")[0])
+      $(@paste_element_id).html("#{pastedText}")
+
+      @setupElementsClasses $(@paste_element_id), ()=>
+        nodes = $($(@paste_element_id).html()).insertAfter($(@aa))
+        $(@paste_element_id).remove()
         #set caret on newly created node
         last_node = nodes.last()[0]
         num = last_node.childNodes.length
@@ -826,7 +830,7 @@
     utils.log ("POSITION FOR TOOLTIP")
     #utils.log $(element)
     element = $(element)
-    return if !element || element[0].tagName is "LI"
+    return if !element || _.isEmpty(element) || element[0].tagName is "LI"
     @tooltip_view.hide()
     return unless _.isEmpty( element.text() )
     @positions = element.offset()
@@ -852,7 +856,6 @@
     name = n.nodeName.toLowerCase()
     switch name
       when "p", "pre", "div"
-        #utils.log n
         unless $(n).hasClass("graf--mixtapeEmbed")
           $(n).removeClass().addClass("graf graf--#{name}")

@@ -917,11 +920,11 @@
       #clean context and wrap text nodes
       @cleanContents(@element)
       @wrapTextNodes(@element)
+
       #setup classes
       _.each  @element.children(), (n)=>
         name = $(n).prop("tagName").toLowerCase()
         n = @addClassesToElement(n)
-
         @setElementName(n)

       @setupLinks(@element.find("a"))
@@ -1001,7 +1004,6 @@
                         return null
                     ]

-    utils.log(@element)
     if @element.exists()
       utils.log "CLEAN HTML #{@element[0].tagName}"
       @element.html(s.clean_node( @element[0] ))


floating text is now centered around the mouse

--- app/assets/javascripts/pirates/game.js.coffee
+++ app/assets/javascripts/pirates/game.js.coffee
@@ -98,7 +98,7 @@
         # already in watchlist
       else
         $('#watchlist').append "
 #{selection}
"
-        hoveringSelection = $ "
#{selection}
"
+        hoveringSelection = $ "
#{selection}
"

         dropdownToggle = $ '#watchlist-dropdown'
         hoveringSelection.css({
@@ -106,10 +106,21 @@
           top:  window.mouse.y
           left: window.mouse.x
           display: 'block'
+          visibility: 'hidden'
           opacity: 1
+          'z-index': 5000
         })
-        .appendTo('body')
-        .animate({
+
+        hoveringSelection.appendTo('body')
+
+        $span = hoveringSelection.children('span:first')
+        hoveringSelection.css({
+          top: window.mouse.y - ($span.height()/2)
+          left: window.mouse.x - ($span.width()/2)
+          visibility: 'visible'
+        })
+
+        hoveringSelection.animate({
             top: dropdownToggle.offset().top
             left: dropdownToggle.offset().left
             opacity: 0.0


add previous direction commands, rename command to not seen as normal command.

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -909,21 +909,24 @@
     if (charsMax > 1)
       options =
         autoConfirmTimeout: @getConfig("findConfirmByTimeout")
-        onChange: (@chars) => @highlightTextInCursorRows(@chars, "pre-confirm")
+        onChange: (@preConfirmedChars) => @highlightTextInCursorRows(@preConfirmedChars, "pre-confirm")
         onCancel: =>
           @vimState.highlightFind.clearMarkers()
           @cancelOperation()
         commands:
-          "vim-mode-plus:find-next": =>
-            if @chars
-              @count += 1
-              @highlightTextInCursorRows(@chars, "pre-confirm")
+          "vim-mode-plus:find-next-pre-confirmed": => @findPreConfirmed(+1)
+          "vim-mode-plus:find-previous-pre-confirmed": =>  @findPreConfirmed(-1)

     options ?= {}
     options.purpose = "find"
     options.charsMax = charsMax

     @focusInput(options)
+
+  findPreConfirmed: (delta) ->
+    if @preConfirmedChars
+      @count += delta
+      @highlightTextInCursorRows(@preConfirmedChars, "pre-confirm")

   repeatIfNecessary: ->
     if @getConfig("reuseFindForRepeatFind")


Check in the draft implementation of redis session storage

--- library/nucleus/plumbs.coffee
+++ library/nucleus/plumbs.coffee
@@ -24,6 +24,7 @@
 ###

 _ = require "lodash"
+assert = require "assert"
 uuid = require "node-uuid"
 asciify = require "asciify"
 connect = require "connect"
@@ -35,46 +36,36 @@
 http = require "http"
 util = require "util"

-# This middleware captures the relevant (and unrelevant) data when
-# the request comes in and attaches the data to the request so it
-# can later be used by whoever might needs this. At the moment it
-# just captures some rudimentary data, such as timestamp and UUID.
-module.exports.capture = (kernel) ->
-    (request, response, next) ->
-        sent = request.headersSent
-        request.uuid = uuid.v1()
-        request.date = new Date
-        request.kernel = kernel
-        next() unless sent
+{RedisStorage} = require "../exposure/session"

-# This middleware is a little handy utility that merges the set
-# of parameters, specifically the ones transferred via query or
-# via body mechanism into one object that can be used to easily
-# access the parameters without thinking about transfer mechanism.
-module.exports.params = (kernel) ->
-    (request, response, next) ->
-        body = request.body or {}
-        query = request.query or {}
-        request.params = Object.create {}
-        _.extend request.params, query
-        _.extend request.params, body
-        next() unless request.headersSent
+# This middleware is really a wrapper around the `Connect` logger
+# that pipes all the request logs to the `Winston` instances that
+# is used throughout the framework to provide logging capabilities.
+# The format is takes from the `NConf` config or the default `dev`.
+module.exports.logger = (kernel) ->
+    levelKey = "log:request:level"
+    formatKey = "log:request:format"
+    format = nconf.get(formatKey) or "dev"
+    level = nconf.get(levelKey) or "debug"
+    filter = (string) -> string.replace "\n", ""
+    writer = (data) -> logger.log level, filter data
+    options = stream: write: writer
+    options.format = format
+    connect.logger options

-# A middleware that adds a `redirect` method to the response object.
-# This redirects to the supplied URL with the 302 status code and
-# the corresponding reason phrase. This method also sets some of the
-# necessary headers, such as nullary `Content-Length` and some other.
-module.exports.redirect = (kernel) ->
-    (request, response, next) ->
-        response.redirect = (url, status) ->
-            relocated = status or 302
-            codes = http.STATUS_CODES
-            message = codes[relocated]
-            response.setHeader "Location", url
-            response.setHeader "Content-Length", 0
-            response.writeHead relocated, message
-            response.end undefined
-        next() unless request.headersSent
+# This middleware is really a wrapper around the `Connect` session
+# middleware. The reason it wraps it is to automatically configure
+# the session storage using the kernel and scoping configuration
+# data. It is automatically connected by the kernel instance.
+module.exports.session = (kernel) ->
+    options = nconf.get "session"
+    redis = _.isObject nconf.get "redis"
+    noSession = "No session settings in scope"
+    useRedis = "Using Redis session storage engine"
+    options.store = RedisStorage.obtain() if redis
+    assert _.isObject(options), noSession
+    logger.info useRedis.blue if redis
+    return connect.session options

 # A middleware that adds a `send` method to the response object.
 # This allows for automatic setting of `Content-Type` headers
@@ -110,28 +101,43 @@
             _.find merged, handles
         next() unless request.headersSent

-# This middleware is really a wrapper around the `Connect` logger
-# that pipes all the request logs to the `Winston` instances that
-# is used throughout the framework to provide logging capabilities.
-# The format is takes from the `NConf` config or the default `dev`.
-module.exports.logger = (kernel) ->
-    levelKey = "log:request:level"
-    formatKey = "log:request:format"
-    format = nconf.get(formatKey) or "dev"
-    level = nconf.get(levelKey) or "debug"
-    filter = (string) -> string.replace "\n", ""
-    writer = (data) -> logger.log level, filter data
-    options = stream: write: writer
-    options.format = format
-    connect.logger options
+# A middleware that adds a `redirect` method to the response object.
+# This redirects to the supplied URL with the 302 status code and
+# the corresponding reason phrase. This method also sets some of the
+# necessary headers, such as nullary `Content-Length` and some other.
+module.exports.redirect = (kernel) ->
+    (request, response, next) ->
+        response.redirect = (url, status) ->
+            relocated = status or 302
+            codes = http.STATUS_CODES
+            message = codes[relocated]
+            response.setHeader "Location", url
+            response.setHeader "Content-Length", 0
+            response.writeHead relocated, message
+            response.end undefined
+            next() unless request.headersSent

-# This middleware is really a wrapper around the `Connect` session
-# middleware. The reason it wraps it is to automatically configure
-# the session storage using the kernel and scoping configuration
-# data. It is automatically connected by the kernel instance.
-module.exports.session = (kernel) ->
-    options = nconf.get "session"
-    defined = _.isObject options
-    noSession = "No session settings in scope"
-    throw new Error noSession unless defined
-    connect.session options
+# This middleware is a little handy utility that merges the set
+# of parameters, specifically the ones transferred via query or
+# via body mechanism into one object that can be used to easily
+# access the parameters without thinking about transfer mechanism.
+module.exports.params = (kernel) ->
+    (request, response, next) ->
+        body = request.body or {}
+        query = request.query or {}
+        request.params = Object.create {}
+        _.extend request.params, query
+        _.extend request.params, body
+        next() unless request.headersSent
+
+# This middleware captures the relevant (and unrelevant) data when
+# the request comes in and attaches the data to the request so it
+# can later be used by whoever might needs this. At the moment it
+# just captures some rudimentary data, such as timestamp and UUID.
+module.exports.capture = (kernel) ->
+    (request, response, next) ->
+        sent = request.headersSent
+        request.uuid = uuid.v1()
+        request.date = new Date
+        request.kernel = kernel
+        next() unless sent


Revert "total column tooltip visible when column is first"

This reverts commit 2c1521a2f02ed5263bbb1b0fbceaeef892612c8a.

Change-Id: I1bba3905d1f97c60774efad3b61b54f03052342d
Reviewed-on: https://gerrit.instructure.com/65332
Reviewed-by: Dylan Ross <7374864f039b76a5b01d8407f92828e3f173309e@instructure.com>
Tested-by: Jenkins
QA-Review: Jason Carter <976bfc4bed0555ff96b3266aea18e7b17a9d471c@instructure.com>
Product-Review: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>

--- app/coffeescripts/gradebook2/Gradebook.coffee
+++ app/coffeescripts/gradebook2/Gradebook.coffee
@@ -783,7 +783,7 @@
         percentage: percentage
       if columnDef.type == 'total_grade'
         templateOpts.warning = @totalGradeWarning
-        templateOpts.lastColumn = !@totalColumnInFront
+        templateOpts.lastColumn = true
         templateOpts.showPointsNotPercent = @displayPointTotals()
         templateOpts.hideTooltip = @weightedGroups() and not @totalGradeWarning
       groupTotalCellTemplate templateOpts
@@ -933,14 +933,6 @@
     # contain them all
     fixMaxHeaderWidth: ->
       @$grid.find('.slick-header-columns').width(1000000)
-
-    # This is because of the default behaviour of slickgrid.  When it
-    # initialized itself, it writes CSS directly to its elements.  For
-    # our frozen columns, it was casuing the tooltips to be buried and
-    # cut off.
-    fixOverflowVisiblity: ->
-      @$grid.find('.slick-viewport').css("overflow", "visible")
-      @$grid.find('.container_0').css("overflow", "visible")

     # SlickGrid doesn't have a blur event for the grid, so this mimics it in
     # conjunction with a click listener on <body />. When we 'blur' the grid
@@ -1007,8 +999,6 @@
           else if columnDef.minimized
             @unminimizeColumn($columnHeader)

-      @fixOverflowVisiblity()
-
       @keyboardNav = new GradebookKeyboardNav(@grid, @$grid)
       @keyboardNav.init()
       keyBindings = @keyboardNav.keyBindings


fix(renderer): asset directory not correct

When use permalink style like `:category/:title/`, which is introduced
in ae0503a, asset directory will be created under category directory.

TODO, refact all these modules

--- lib/render.coffee
+++ lib/render.coffee
@@ -21,8 +21,11 @@
     dest = path.join post.destination, post.permalink
     @render_file src, dest, post
     # assets
+    #dest = path.join(dest, if path.extname dest then '' else 'index.html')
+    # use current directory if permalink don't have filename
+    dest = if path.extname dest then path.dirname dest else dest
     assets = path.join path.dirname(post.src), 'assets'
-    fs.copy "#{assets}", "#{path.dirname dest}/assets"
+    fs.copy "#{assets}", "#{dest}/assets"

   src = path.join site.theme_path, site.theme, 'index'
   dest = path.join site.destination, 'index.html'


OX-590 Make more consequent use of timeFactor

--- app/assets/javascripts/oxalis/controller/arbitrary_controller.coffee
+++ app/assets/javascripts/oxalis/controller/arbitrary_controller.coffee
@@ -110,16 +110,16 @@
       "alt + space"   : (timeFactor) => @cam.move [0, 0, -getVoxelOffset(timeFactor)]

       #Rotate in distance
-      "left"          : (timeFactor) => @cam.yawDistance @model.user.rotateValue
-      "right"         : (timeFactor) => @cam.yawDistance -@model.user.rotateValue
-      "up"            : (timeFactor) => @cam.pitchDistance -@model.user.rotateValue
-      "down"          : (timeFactor) => @cam.pitchDistance @model.user.rotateValue
+      "left"          : (timeFactor) => @cam.yawDistance @model.user.rotateValue * timeFactor
+      "right"         : (timeFactor) => @cam.yawDistance -@model.user.rotateValue * timeFactor
+      "up"            : (timeFactor) => @cam.pitchDistance -@model.user.rotateValue * timeFactor
+      "down"          : (timeFactor) => @cam.pitchDistance @model.user.rotateValue * timeFactor

       #Rotate at centre
-      "shift + left"  : (timeFactor) => @cam.yaw @model.user.rotateValue
-      "shift + right" : (timeFactor) => @cam.yaw -@model.user.rotateValue
-      "shift + up"    : (timeFactor) => @cam.pitch @model.user.rotateValue
-      "shift + down"  : (timeFactor) => @cam.pitch -@model.user.rotateValue
+      "shift + left"  : (timeFactor) => @cam.yaw @model.user.rotateValue * timeFactor
+      "shift + right" : (timeFactor) => @cam.yaw -@model.user.rotateValue * timeFactor
+      "shift + up"    : (timeFactor) => @cam.pitch @model.user.rotateValue * timeFactor
+      "shift + down"  : (timeFactor) => @cam.pitch -@model.user.rotateValue * timeFactor

       #Zoom in/out
       "i"             : (timeFactor) => @cam.zoomIn()


updates for transcription proposal

updates for transcription proposal

--- app/pages/get-involved/call-for-projects.cjsx
+++ app/pages/get-involved/call-for-projects.cjsx
@@ -41,11 +41,11 @@

     ## Call for Text Transcription Project Proposals

-    You can see examples of our current text transcription projects at Zooniverse.org. These include [AnnoTate](https://anno.tate.org.uk/#/), where volunteers transcribe twentieth-century British artists’ sketchbooks and papers, and [Shakespeare’s World](https://www.shakespearesworld.org/#/), where volunteers transcribe documents produced in and around Shakespeare’s lifetime (sixteenth and seventeenth century). The resulting datasets will be incorporated into the Tate and Folger Shakespeare Library’s respective content management systems (CMS), and will also be used for research by humanities specialists. We are particularly keen to work on projects where the data can be incorporated into an existing CMS and used for research purposes.
+    You can see examples of our current text transcription projects at [Zooniverse.org](http://zooniverse.org/). These include [AnnoTate](https://anno.tate.org.uk/#/), where volunteers transcribe twentieth-century British artists’ sketchbooks and papers; [Decoding the Civil War](https://www.decodingthecivilwar.org), in which volunteers transcribe telegrams from the American Civil War, and [Shakespeare’s World](https://www.shakespearesworld.org/#/), where volunteers transcribe documents produced in and around Shakespeare’s lifetime (sixteenth and seventeenth century). The resulting datasets will be incorporated into the Tate and Folger Shakespeare Library’s respective content management systems (CMS), and will also be used for research by humanities specialists. We are particularly keen to work on projects where the data can be incorporated into an existing CMS and used for research purposes.

     ### Project Selection

-    Using our free [Project Builder](/lab) you can now build your own Zooniverse project with a set of tried and tested tools such as questions (yes/no; multiple choice) and region marking or drawing tools. However, while we have created transcription projects such as AnnoTate and Shakespeare’s World, we have not made text transcription tools available on the free Project Builder. Before we build this functionality into the Project Builder we are seeking one or more transcription projects that will help us test and expand the remit of our text transcription tools. These projects need not replicate the functionality of AnnoTate or Shakespeare’s World. They could involve letter-by-letter transcription using a bespoke keyboard, as in Ancient Lives, the first Zooniverse transcription project, which is now retired.
+    Using our free [Project Builder](/lab) you can now build your own Zooniverse project with a set of tried and tested tools such as questions (yes/no; multiple choice) and region marking or drawing tools. More recently we’ve released transcription transcription tools, but are keen to expand their functionality. To that end, we are seeking one or more transcription projects that will help us test and expand the remit of our text transcription tools. These projects need not replicate the functionality of AnnoTate, Decoding the Civil War or Shakespeare’s World. They could involve letter-by-letter transcription using a bespoke keyboard, as in Ancient Lives, the first Zooniverse transcription project, which is now retired.

     ### Selection Criteria:


Fix cover art thumbnail in title details Here, in a read-only list, we can no longer wrap the image tag for the thumbnail inside a div tag inside the list tag. Also, it seems the image tag doesn't have to be the first child of the list tag. If it appears as the last, the thumbnail will still appear in the listview. Also, we adorn the list tag with a class name and an id number.

--- src/opac/edit_hold.coffee
+++ src/opac/edit_hold.coffee
@@ -23,8 +23,7 @@
 	$.fn.title_details = ($img) ->

 		tpl_title_details = _.template '''
-

-

+		<li class="title_details" id="target_id_<%= target_id %>">

Title:                <%= b.title            %>

Author:               <%= b.author           %>

@@ -90,11 +89,11 @@
 		hold = @closest('.plugin').data 'hold'

 		@openils "title details ##{hold.target}", 'search.biblio.record.html', hold.target, (htmlmarc) ->
-			@html(tpl_title_details {
+			@html(tpl_title_details
 				b: marc_text htmlmarc # Convert MARC HTML to MARC object.
 				target_id: hold.target
 				hold_id: hold.id or 0
-			})
+			)
 			.find('.value').each ->
 				# Remove empty values.
 				# FIXME: removal is not perfect, leaves empty divs behind.
@@ -103,13 +102,11 @@
 			# Remove thumbnail container from list view is there is no thumbnail image
 			if $img.get(0).naturalHeight > 0
 				# FIXME: This is an attempt to double the size of the thumbnail,
-				# but it is stymied by the fixed size of the outer conainer.
+				# but it is stymied by the fixed size of the outer container.
 				#h = $img.get(0).naturalHeight
 				#w = $img.get(0).naturalWidth
 				#$img.height(2 * h).width(2 * w)
-				$('.art_box', @).append $img.attr('title', '')
-			else
-				$('.art_box', @).remove()
+				$('.title_details', @).prepend $img.attr('title', '')

 			@listview 'refresh'
 			return


Add .cursor-line-no-selection decoration for backward compatibility

--- src/editor.coffee
+++ src/editor.coffee
@@ -1251,6 +1251,7 @@
     cursor = new Cursor(editor: this, marker: marker)
     @cursors.push(cursor)
     @addDecorationForMarker(marker, type: ['gutter', 'line'], class: 'cursor-line', onlyHead: true)
+    @addDecorationForMarker(marker, type: ['gutter', 'line'], class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
     @addDecorationForMarker(marker, type: ['gutter', 'line'], class: 'selection-line', onlyNonEmpty: true)
     @emit 'cursor-added', cursor
     cursor


preventDefault -> cancel()

preventDefault -> cancel()

--- src/editor.coffee
+++ src/editor.coffee
@@ -629,8 +629,8 @@

   # Public: For each selection, replace the selected text with the given text.
   #
-  # Emits: `will-insert-text -> ({text, preventDefault})` before the text has
-  # been inserted. Calling `preventDefault` will prevent the text from being
+  # Emits: `will-insert-text -> ({text, cancel})` before the text has
+  # been inserted. Calling `cancel` will prevent the text from being
   # inserted.
   # Emits: `did-insert-text -> ({text})` after the text has been inserted.
   #
@@ -640,8 +640,8 @@
   # Returns a {Bool} indicating whether or not the text was inserted.
   insertText: (text, options={}) ->
     willInsert = true
-    preventDefault = -> willInsert = false
-    @emit('will-insert-text', {preventDefault, text})
+    cancel = -> willInsert = false
+    @emit('will-insert-text', {cancel, text})

     if willInsert
       options.autoIndentNewline ?= @shouldAutoIndent()


support italics, underline, strike

support italics, underline, strike

--- src/editor.coffee
+++ src/editor.coffee
@@ -124,30 +124,35 @@

   # applyAttribute: (TandemRange range, Object attribute) ->
   # applyAttribute: (Number startIndex, Number length, attribute) ->
-  applyAttribute: (startIndex, length, attribute, emitEvent = true) ->
+  applyAttribute: (startIndex, length, attributes, emitEvent = true) ->
     @ignoreDomChanges = true
-    if _.isNumber(startIndex)
-      range = new Tandem.Range(this, startIndex, startIndex + length)
-    else
-      [range, attribute] = [startIndex, length]
+    if !_.isNumber(startIndex)
+      [range, attributes] = [startIndex, length]
       startIndex = range.start.getIndex()
       length = range.end.getIndex() - startIndex
     this.preserveSelection(range, 0, =>
-      range.splitEnds()
-      _.each(range.groupNodesByLine(), (elems) =>
-        return if elems.length == 0
-        container = @iframeDoc.createElement('b')
-        elems[0].parentNode.insertBefore(container, elems[0])
-        _.each(elems, (elem) ->
-          container.appendChild(elem)
+      for attr,value of attributes
+        range = new Tandem.Range(this, startIndex, startIndex + length) unless range?
+        range.splitEnds()
+        _.each(range.groupNodesByLine(), (elems) =>
+          return if elems.length == 0
+          switch (attr)
+            when 'bold'       then container = @iframeDoc.createElement('b')
+            when 'italic'     then container = @iframeDoc.createElement('i')
+            when 'strike'     then container = @iframeDoc.createElement('s')
+            when 'underline'  then container = @iframeDoc.createElement('u')
+            else return
+          elems[0].parentNode.insertBefore(container, elems[0])
+          _.each(elems, (elem) ->
+            container.appendChild(elem)
+          )
         )
-      )
     )
     if emitEvent
       docLength = @iframeDoc.body.textContent.length + @iframeDoc.body.childNodes.length - 1
       deltas = []
       deltas.push(new JetRetain(0, startIndex)) if startIndex > 0
-      deltas.push(new JetRetain(startIndex, startIndex + length, attribute))
+      deltas.push(new JetRetain(startIndex, startIndex + length, attributes))
       deltas.push(new JetRetain(startIndex + length, docLength)) if startIndex + length < docLength
       delta = new JetDelta(docLength, docLength, deltas)
       this.emit(this.events.API_TEXT_CHANGE, delta)


reverse order of selection and delta tracking

--- src/editor.coffee
+++ src/editor.coffee
@@ -122,14 +122,15 @@

   deleteAt: (index, length, emitEvent = true) ->
     this.doSilently( =>
-      delta = this.trackDelta( =>
-        @selection.preserve( =>
+      @selection.preserve( =>
+        delta = this.trackDelta( =>
           this.keepNormalized( =>
             @doc.deleteText(index, length)
           )
         )
-      )
-      this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent
+        this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent
+        return delta
+      )
     )

   doSilently: (fn) ->
@@ -141,14 +142,15 @@
   # format: (Number index, Number length, String name, Mixed value) ->
   format: (index, length, name, value, emitEvent = true) ->
     this.doSilently( =>
-      delta = this.trackDelta( =>
-        @selection.preserve( =>
+      @selection.preserve( =>
+        delta = this.trackDelta( =>
           this.keepNormalized( =>
             @doc.format(index, length, name, value)
           )
         )
-      )
-      this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent
+        this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent
+        return delta
+      )
     )

   getDelta: ->
@@ -159,14 +161,15 @@

   insertAt: (index, text, emitEvent = true) ->
     this.doSilently( =>
-      delta = this.trackDelta( =>
-        @selection.preserve( =>
+      @selection.preserve( =>
+        delta = this.trackDelta( =>
           this.keepNormalized( =>
             @doc.insertText(index, text)
           )
         )
-      )
-      this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent
+        this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent
+        return delta
+      )
     )

   setSelection: (range) ->
@@ -184,8 +187,8 @@

   update: (emitEvent = true) ->
     this.doSilently( =>
-      delta = this.trackDelta( =>
-        @selection.preserve( =>
+      @selection.preserve( =>
+        delta = this.trackDelta( =>
           Scribe.Document.normalizeHtml(@root)
           lines = @doc.lines.toArray()
           lineNode = @root.firstChild
@@ -204,9 +207,9 @@
             newLine = @doc.appendLine(lineNode)
             lineNode = lineNode.nextSibling
         )
-        @selection.update(true)
-      )
-      this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent and !delta.isIdentity()
+        this.emit(ScribeEditor.events.TEXT_CHANGE, delta) if emitEvent and !delta.isIdentity()
+        return delta
+      )
     )


Update spec passing

Update spec passing

--- spec/adaptorMethods/updateSpec.coffee
+++ spec/adaptorMethods/updateSpec.coffee
@@ -1,51 +1,93 @@
+create = require '../../src/adaptorMethods/create'
 update = require '../../src/adaptorMethods/update'
+find = require '../../src/adaptorMethods/find'
 Promise = require 'promise'
 _ = require 'lodash'
+redis = require 'redis'

 describe 'oomphRedisAdaptor#update', ->

-  beforeAll ->
-    parentObject =
-      redis:
-        multi: -> {
-          zrem: -> null
-          exec: -> null
-        }
-      find: (id) ->
-        new Promise (resolve,reject) ->
-          if id == 'invalidID'
-            resolve(null)
-          else
-            resolve({})
-      classAttributes:
+  beforeAll (done) ->
+    @redis = redis.createClient(1111, 'localhost')
+    done()
+
+  beforeEach ->
+    @parentObject =
+      className: 'TestUpdateClass'
+      redis: @redis
+      create: create
+      find: find
+      classAttributes:
+        name:
+          dataType: 'string'
+        url:
+          dataType: 'string'
+          url: true
+          urlBaseAttribute: 'name'
         one:
           dataType: 'integer'
-    @update = update.bind(parentObject)
-
-  #beforeEach (done) ->
-  #  ref1 = @referenceModel.create(secondId: 'ida')
-  #  ref2 = @referenceModel.create(secondId: 'idb')
-  #  ref3 = @referenceModel.create(secondId: 'idc')
-  #  Promise.all([ref1,ref2,ref3]).done (refs) =>
-  #    @ref1Id = refs[0].id
-  #    @ref2Id = refs[1].id
-  #    @ref3Id = refs[2].id
-  #    testProps =
-  #      one: '0'
-  #      integer: 1
-  #      identifier: "identifier"
-  #      boolean: true
-  #      reference: "reference"
-  #      manyReferences: [@ref1Id, @ref2Id, @ref3Id]
-  #      searchableText: "Search this"
-  #      sortableString: "first"
-  #      sortableInteger: 1
-  #    @redisObjectClassDataStore.create(testProps).done (testObject) =>
-  #      @testObj = testObject
-  #      done()
+          sortable: true
+        two:
+          dataType: 'integer'
+          sortable: true
+        three:
+          dataType: 'integer'
+          sortable: true
+        integer:
+          dataType: 'integer'
+        identifier:
+          dataType: 'string'
+          identifiable: true
+        reference:
+          dataType: 'reference'
+          referenceModelName: 'Reference'
+        manyReferences:
+          dataType: 'reference'
+          many: true
+          referenceModelName: 'Reference'
+        sortableString:
+          dataType: 'string'
+          sortable: true
+        sortableInteger:
+          dataType: 'integer'
+          sortable: true
+        searchableText:
+          dataType: 'text'
+          searchable: true
+        searchableString:
+          dataType: 'string'
+          searchable: true
+        boolean:
+          dataType: 'boolean'
+    @update = update.bind(@parentObject)
+
+  beforeEach (done) ->
+    # set up exisitng test object to update
+    testProps =
+      one: '0'
+      integer: 1
+      identifier: "identifier"
+      boolean: true
+      reference: "reference"
+      manyReferences: ['ref1Id', 'ref2Id', 'ref3Id']
+      searchableText: "Search this"
+      sortableString: "first"
+      sortableInteger: 1
+    @parentObject.create(testProps).then (createdTestObj) =>
+      @testObj = createdTestObj
+      done()
+
+  afterEach (done) ->
+    @redis.flushdb()
+    done()
+
+  afterAll (done) ->
+    @redis.flushdb()
+    @redis.end()
+    done()

   it 'should return a promise', ->
-    testObjectPromise = @update 'testObjID', url: 'uniqueValue'
+    testObjectPromise = @update @testObj.id, url: 'uniqueValue'
     expect(testObjectPromise).toEqual jasmine.any(Promise)

   it 'should throw an error when the id is not found', (done) ->
@@ -55,113 +97,120 @@
       done()

   it 'should update the object when a change is made', (done) ->
-    testObjectPromise = @update 'testObjID', one: 111
-    testObjectPromise.done (obj) ->
+    testObjectPromise = @update @testObj.id, one: 111
+    testObjectPromise.then (obj) ->
       expect(obj.one).toEqual 111
       done()

-  #it 'should update the relevant sorted set when an integer field is updated', (done) ->
-  #  testObjectPromise = @redisObjectClassDataStore.update @testObj.id, integer: 9
-  #  testObjectPromise.done (obj) =>
-  #    @redisObjectClassDataStore.redis.zrangebyscore 'RedisObjectClassDataStore>integer', 0, 10, 'withscores', (err, res) =>
-  #      expect(res).toEqual [@testObj.id, '9']
-  #      done()
-
-  #it 'should update the relevant key-value pair when an identifier field is updated', (done) ->
-  #  testObjectPromise = @redisObjectClassDataStore.update @testObj.id, identifier: 'edited'
-  #  testObjectPromise.done (obj) =>
-  #    multi = @redisObjectClassDataStore.redis.multi()
-  #    multi.get 'RedisObjectClassDataStore#identifier:edited'
-  #    multi.get 'RedisObjectClassDataStore#identifier:identifier'
-  #    multi.exec (err, res) =>
-  #      expect(res[0]).toEqual @testObj.id
-  #      expect(res[1]).toEqual null
-  #      expect(res.length).toEqual 2
-  #      done()
-
-  #it 'should add to a set when an reference field is updated', (done) ->
-  #  pending()
-  #  testObjectPromise = @redisObjectClassDataStore.update(@testObj.id, manyReferences: ['editedId1'])
-  #  testObjectPromise.done (obj) =>
-  #    @redisObjectClassDataStore.redis.smembers 'RedisObjectClassDataStore:' + @testObj.id + '#manyReferences:ReferenceRefs', (err, members) ->
-  #      expect(members).toContain 'editedId1'
-  #      expect(members.length).toEqual 4
-  #      done()
-
-  #it 'removes from a set', (done) ->
-  #  @redisObjectClassDataStore.update(@testObj.id, remove_manyReferences: [@ref1Id]).then (createdObject) =>
-  #    @redis.smembers 'Reference:' + @ref1Id + '#manyReferences:RedisObjectClassDataStoreRefs' , (err, obj) =>
-  #      expect(obj).not.toContain @testObj.id
-  #      done()
-
-  #it 'removes from a set', (done) ->
-  #  @redisObjectClassDataStore.attributes.linkedModel =
-  #    dataType: 'reference'
-  #    many: true
-  #    referenceModelName: 'LinkedModel'
-  #  @redisObjectClassDataStore.create(url: 'one', linkedModel: [@ref1Id, @ref2Id, @ref3Id]).done (testObject) =>
-  #    multi = @redisObjectClassDataStore.redis.multi()
-  #    spyOn(@redisObjectClassDataStore.redis, 'multi').and.returnValue(multi)
-  #    spyOn(multi, 'srem')
-  #    @redisObjectClassDataStore.update(testObject.id, remove_linkedModel: [@ref1Id, @ref3Id]).then (createdObject) =>
-  #      expect(multi.srem).toHaveBeenCalledWith('RedisObjectClassDataStore:' + createdObject.id + '#linkedModel:LinkedModelRefs', @ref1Id, @ref3Id  )
-  #      expect(multi.srem).toHaveBeenCalledWith('LinkedModel:' + @ref1Id + '#linkedModel:RedisObjectClassDataStoreRefs', createdObject.id)
-  #      expect(multi.srem).toHaveBeenCalledWith('LinkedModel:' + @ref3Id + '#linkedModel:RedisObjectClassDataStoreRefs', createdObject.id)
-  #      done()
-
-  #it 'should update the relevant set when a boolean field is updated', (done) ->
-  #  testObjectPromise = @redisObjectClassDataStore.update @testObj.id, boolean: false
-  #  testObjectPromise.done (obj) =>
-  #    multi = @redisObjectClassDataStore.redis.multi()
-  #    multi.zrange 'RedisObjectClassDataStore#boolean:false', 0, -1
-  #    multi.zrange 'RedisObjectClassDataStore#boolean:true', 0, -1
-  #    multi.exec (err, res) =>
-  #      expect(res[0]).toEqual [@testObj.id]
-  #      expect(res[1]).toEqual []
-  #      expect(res.length).toEqual 2
-  #      done()
-
-  #it 'should remove partial words sets when there is a searchable field', (done) ->
-  #  spyOn(@redisObjectClassDataStore.redis, 'zrem').and.callThrough()
-  #  @redisObjectClassDataStore.update(@testObj.id, searchableText: null).then (createdObject) =>
-  #    calledArgs = @redisObjectClassDataStore.redis.zrem.calls.allArgs()
-  #    keysCalled = []
-  #    for call in calledArgs
-  #      keysCalled.push call[0]
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/s')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/se')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/sea')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/sear')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/search')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/t')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/th')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/thi')
-  #    expect(keysCalled).toContain('RedisObjectClassDataStore#searchableText/this')
-  #    done()
-
-  #it 'should update the relevant sorted set when a sortable string is updated', (done) ->
-  #  testObjectPromise = @redisObjectClassDataStore.update @testObj.id, sortableString: 'second'
-  #  testObjectPromise.done (obj) =>
-  #    @redis.zrange "RedisObjectClassDataStore>sortableString", 0, -1, (error, list) =>
-  #      expect(list).toEqual [@testObj.id]
-  #      done()
-
-  #it 'should update the object', (done) ->
-  #  updateProps = { one: 1, integer: 2, identifier: 'newidentifier', sortableString: 'second', sortableInteger: 2, searchableText: 'new Search this', boolean: false }
-  #  _.assign(@testObj, updateProps)
-  #  testObjectPromise = @redisObjectClassDataStore.update @testObj.id, updateProps
-  #  testObjectPromise.done (obj) =>
-  #    @redisObjectClassDataStore.find(@testObj.id).done (returnValue) =>
-  #      expect(returnValue).toEqual @testObj
-  #      done()
-
-  #describe '"remove_" prefix', ->
-  #  it 'should remove values from a set when an reference is updated', (done) ->
-  #    testObjectPromise = @redisObjectClassDataStore.update @testObj.id, remove_manyReferences: [@ref2Id, '2']
-  #    testObjectPromise.done (obj) =>
-  #      @redisObjectClassDataStore.redis.smembers 'RedisObjectClassDataStore:' + @testObj.id + '#manyReferences:ReferenceRefs', (err, members) =>
-  #        expect(members).toContain @ref1Id
-  #        expect(members).toContain @ref3Id
-  #        expect(members.length).toEqual 2
-  #        done()
-
+  it 'should update the relevant sorted set when an integer field is updated', (done) ->
+    testObjectPromise = @update @testObj.id, integer: 9
+    testObjectPromise.then (obj) =>
+      @redis.zrangebyscore 'TestUpdateClass>integer', 0, 10, 'withscores', (err, res) =>
+        expect(res).toEqual [@testObj.id, '9']
+        done()
+
+  it 'should update the relevant key-value pair when an identifier field is updated', (done) ->
+    testObjectPromise = @update @testObj.id, identifier: 'edited'
+    testObjectPromise.then (obj) =>
+      multi = @redis.multi()
+      multi.get 'TestUpdateClass#identifier:edited'
+      multi.get 'TestUpdateClass#identifier:identifier'
+      multi.exec (err, res) =>
+        expect(res[0]).toEqual @testObj.id
+        expect(res[1]).toEqual null
+        expect(res.length).toEqual 2
+        done()
+
+  it 'should add to a set when an reference field is updated', (done) ->
+    pending()
+    testObjectPromise = @update(@testObj.id, manyReferences: ['editedId1'])
+    testObjectPromise.then (obj) =>
+      @redis.smembers 'TestUpdateClass:' + @testObj.id + '#manyReferences:ReferenceRefs', (err, members) ->
+        expect(members).toContain 'editedId1'
+        expect(members.length).toEqual 4
+        done()
+
+  it 'removes from a set', (done) ->
+    @update(@testObj.id, remove_manyReferences: [@ref1Id]).then (createdObject) =>
+      @redis.smembers 'Reference:' + @ref1Id + '#manyReferences:TestUpdateClassRefs' , (err, obj) =>
+        expect(obj).not.toContain @testObj.id
+        done()
+
+  it 'removes from a set', (done) ->
+    @parentObject.classAttributes.linkedModel =
+      dataType: 'reference'
+      many: true
+      referenceModelName: 'LinkedModel'
+    @parentObject.create = create.bind(@parentObject)
+    @parentObject.create(url: 'one', linkedModel: [@ref1Id, @ref2Id, @ref3Id]).then (testObject) =>
+      multi = @redis.multi()
+      spyOn(@redis, 'multi').and.returnValue(multi)
+      spyOn(multi, 'srem')
+      @update(testObject.id, remove_linkedModel: [@ref1Id, @ref3Id]).then (createdObject) =>
+        expect(multi.srem).toHaveBeenCalledWith('TestUpdateClass:' + createdObject.id + '#linkedModel:LinkedModelRefs', @ref1Id, @ref3Id  )
+        expect(multi.srem).toHaveBeenCalledWith('LinkedModel:' + @ref1Id + '#linkedModel:TestUpdateClassRefs', createdObject.id)
+        expect(multi.srem).toHaveBeenCalledWith('LinkedModel:' + @ref3Id + '#linkedModel:TestUpdateClassRefs', createdObject.id)
+        done()
+
+  it 'should update the relevant set when a boolean field is updated', (done) ->
+    testObjectPromise = @update @testObj.id, boolean: false
+    testObjectPromise.then (obj) =>
+      multi = @redis.multi()
+      multi.zrange 'TestUpdateClass#boolean:false', 0, -1
+      multi.zrange 'TestUpdateClass#boolean:true', 0, -1
+      multi.exec (err, res) =>
+        expect(res[0]).toEqual [@testObj.id]
+        expect(res[1]).toEqual []
+        expect(res.length).toEqual 2
+        done()
+
+  it 'should remove partial words sets when there is a searchable field', (done) ->
+    spyOn(@redis, 'zrem').and.callThrough()
+    @update(@testObj.id, searchableText: null).then (createdObject) =>
+      calledArgs = @redis.zrem.calls.allArgs()
+      keysCalled = []
+      for call in calledArgs
+        keysCalled.push call[0]
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/s')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/se')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/sea')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/sear')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/search')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/t')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/th')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/thi')
+      expect(keysCalled).toContain('TestUpdateClass#searchableText/this')
+      done()
+
+  it 'should update the relevant sorted set when a sortable string is updated', (done) ->
+    testObjectPromise = @update @testObj.id, sortableString: 'second'
+    testObjectPromise.then (obj) =>
+      @redis.zrange "TestUpdateClass>sortableString", 0, -1, (error, list) =>
+        expect(list).toEqual [@testObj.id]
+        done()
+
+  it 'should update the object', (done) ->
+    updateProps = { one: 1, integer: 2, identifier: 'newidentifier', sortableString: 'second', sortableInteger: 2, searchableText: 'new Search this', boolean: false }
+    _.assign(@testObj, updateProps)
+    testObjectPromise = @update(@testObj.id, updateProps)
+    testObjectPromise.then (obj) =>
+      @parentObject.find(@testObj.id).then (returnValue) =>
+        expect(returnValue.one).toEqual @testObj.one
+        expect(returnValue.integer).toEqual @testObj.integer
+        expect(returnValue.identifier).toEqual @testObj.identifier
+        expect(returnValue.sortableString).toEqual @testObj.sortableString
+        expect(returnValue.sortableInteger).toEqual @testObj.sortableInteger
+        expect(returnValue.searchableText).toEqual @testObj.searchableText
+        expect(returnValue.boolean).toEqual @testObj.boolean
+        done()
+
+  describe '"remove_" prefix', ->
+    it 'should remove values from a set when an reference is updated', (done) ->
+      testObjectPromise = @update @testObj.id, remove_manyReferences: ['ref2Id', '2']
+      testObjectPromise.then (obj) =>
+        @redis.smembers 'TestUpdateClass:' + @testObj.id + '#manyReferences:ReferenceRefs', (err, members) =>
+          expect(members).toContain 'ref1Id'
+          expect(members).toContain 'ref3Id'
+          expect(members.length).toEqual 2
+          done()
+


:white_check_mark: Add tests for variables expressions service

--- spec/expressions-services-spec.coffee
+++ spec/expressions-services-spec.coffee
@@ -65,3 +65,71 @@

     it 'removes the service consumer from the hub', ->
       expect(atom.packages.serviceHub.consumers.length).toEqual(initialConsumersCount)
+
+
+describe 'variable expressions service', ->
+  [initialConsumersCount, initialCount, registry] = []
+
+  beforeEach ->
+    initialConsumersCount = atom.packages.serviceHub.consumers.length
+
+    registry = require '../lib/variables-expressions'
+    registry.registerServiceConsumer()
+
+    initialCount = registry.getExpressions().length
+
+    expect(atom.packages.serviceHub.consumers.length).toEqual(initialConsumersCount + 1)
+
+  afterEach ->
+    registry.disposeServiceConsumer()
+
+  describe 'with a service providing a single expression', ->
+    beforeEach ->
+      expressionObject =
+        name: 'service-test-expression'
+        regexp: 'dummy-expression'
+        handler: (match, expression, context) ->
+
+      atom.packages.serviceHub.provide(
+        'pigments.variable-expression-provider',
+        '0.1.0',
+        expressionObject
+      )
+
+    afterEach ->
+      registry.removeExpression('service-test-expression')
+
+    it 'registers the provided expression in the registry', ->
+      expect(registry.getExpressions().length).toEqual(initialCount + 1)
+
+  describe 'with a service providing an array of expressions', ->
+    beforeEach ->
+      expressionObject1 =
+        name: 'service-test-expression-1'
+        regexp: 'dummy-expression-1'
+        handler: (match, expression, context) ->
+
+      expressionObject2 =
+        name: 'service-test-expression-2'
+        regexp: 'dummy-expression-2'
+        handler: (match, expression, context) ->
+
+      atom.packages.serviceHub.provide(
+        'pigments.variable-expression-provider',
+        '0.1.0',
+        [expressionObject1, expressionObject2]
+      )
+
+    afterEach ->
+      registry.removeExpression('service-test-expression-1')
+      registry.removeExpression('service-test-expression-2')
+
+    it 'registers the provided expression in the registry', ->
+      expect(registry.getExpressions().length).toEqual(initialCount + 2)
+
+  describe 'when disposed', ->
+    beforeEach ->
+      registry.disposeServiceConsumer()
+
+    it 'removes the service consumer from the hub', ->
+      expect(atom.packages.serviceHub.consumers.length).toEqual(initialConsumersCount)


Try to save via API instead of database

Try to save via API instead of database

--- scripts/scheduled_posts.coffee
+++ scripts/scheduled_posts.coffee
@@ -1,4 +1,7 @@
+_ = require 'underscore'
 Q = require 'bluebird-q'
+requestBluebird = require 'superagent-bluebird-promise'
+request = require 'superagent'
 mongojs = require 'mongojs'
 fs = require 'fs'
 path = require 'path'
@@ -15,18 +18,27 @@
 # Connect to database
 db = mongojs(process.env.MONGOHQ_URL, ['articles'])

+articlesToPublish = []
 db.articles.find(
   scheduled_publish_at: { $lt: new Date() }
-).forEach (err, doc) ->
+).on('data', (doc) ->
   if !doc
     debug 'Scheduled publication finished'
     process.exit()
-  if err
-    exit err
-  doc.published = true
-  doc.published_at = moment(doc.scheduled_publish_at).toDate()
-  doc.scheduled_publish_at = null
-  db.articles.save doc
+  articlesToPublish.push doc
+).on 'end', ->
+  db.close()
+  Q.all(for article in articlesToPublish
+    requestBluebird
+      .post("#{process.env.API_URL}/articles/#{article._id}")
+      .set('X-Xapp-Token': process.env.ACCESS_TOKEN)
+      .send
+        published: true
+        published_at: moment(article.scheduled_publish_at).toDate()
+        scheduled_published_at: null
+      .promise()
+  ).done (responses) =>
+    console.log responses

 exit = (err) ->
   console.error "ERROR", err


Create choice and get-by-competitionId routes

--- resources/choices.coffee
+++ resources/choices.coffee
@@ -1,8 +1,46 @@
 {models, Resource, filters} = require '../resource_helper'
+
+crud = require 'bloops/crud'
+errors = require 'bloops/errors'

 ChoiceResource = new Resource
   resourceNameMany: 'choices'
   resourceNameOne: 'choice'
   model: models.choice

+  getEndpoints: -> [
+    'show'
+    'update'
+    'destroy'
+    'patch'
+    'index'
+    create
+    getByCompetition
+  ]
+
+create =
+
+_baseCreate = crud.create.handler
+create =
+  route: '/'
+  method: ['POST', 'PUT']
+  filters: [filters.FromJson]
+  handler: ->
+    unless @params.name? and @params.competitionId?
+      throw new errors.UserError('must provide name and competitionId')
+    @params.competition_id = @params.competitionId
+    delete @params.competitionId
+    _baseCreate.call(@)
+
+getByCompetition =
+  route: '/by-competition/:competitionId'
+  method: 'GET'
+  filters: [filters.FromUrlParams]
+  handler: ->
+    competitionId = @params.competitionId
+    unless competitionId
+      throw new errors.UserError('must provide competitionId')
+
+    @api.list competition_id: competitionId
+
 module.exports = ChoiceResource


ace: Added Cmd+Shift+C key combo to compile app.

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -82,6 +82,13 @@
         win   : 'Ctrl-Shift-S'
         mac   : 'Command-Shift-S'
       exec    : => @requestSaveAs()
+
+    @editor.commands.addCommand
+      name    : 'compileAndRun'
+      bindKey :
+        win   : 'Ctrl-Shift-C'
+        mac   : 'Command-Shift-C'
+      exec    : => @getDelegate().compileAndRun()

     file = @getData()


Intuit wanted a small change.

Intuit wanted a small change.

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -434,7 +434,7 @@

-
Intuit will award an extra $2,500 to the team that presents the best use of their API.

+
Intuit will award an extra $2,500 to the team that presents the best use of the QuickBooks API.


payment: show paypal form if only there is no subscription

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -89,15 +89,16 @@

   filterViews: ->

-    { FREE }  = PaymentWorkflow.planTitle
-    { MONTH } = PaymentWorkflow.planInterval
+    { FREE }   = PaymentWorkflow.planTitle
+    { MONTH }  = PaymentWorkflow.planInterval
+    { KODING } = PaymentWorkflow.provider
     { currentPlan, planTitle, planInterval, provider } = @state

     @yearPriceMessage.hide()  if planInterval is MONTH

     # no need to show those views when they are
     # downgrading to free account.
-    if selectedPlan = planTitle is FREE
+    if planTitle is FREE
       @securityNote.hide()
       @existingCreditCardMessage.hide()
       @yearPriceMessage.hide()
@@ -110,9 +111,7 @@
       @form.show()
       @existingCreditCardMessage.hide()

-    # switch provider
-    #   when 'stripe' then @paypalButton.hide()
-    #   when 'paypal' then @submitButton.hide()
+    @paypalForm.destroy()  unless provider is KODING

   initForm: ->


Addresses #6. Emits an event when the view is fully shown and is visible.

--- scripts/dash-modal/view.coffee
+++ scripts/dash-modal/view.coffee
@@ -28,7 +28,7 @@
     $('body').addClass('prevent-scrolling')
     @$('[data-id=modal]').addClass('in')
     @container.html(@el)
-    @$el.show()
+    @$el.show(=> @view.trigger('showModalComplete'))
     @

   hide: =>


Clean filter.coffee

Clean filter.coffee

--- src/filter.coffee
+++ src/filter.coffee
@@ -1,13 +1,11 @@
-
 params = (options) ->
-  return {
-    url: "https://stream.twitter.com/#{options.version}/statuses/filter.json"
-    oauth: options.oauth
-    form: include_entities: true
-  }
+  url: "https://stream.twitter.com/#{options.version}/statuses/filter.json"
+  oauth: options.oauth
+  form: include_entities: true

 module.exports = (options, beautify, validate, counter) ->
   params = params options
+
   return (fn) ->
     params.form.track = options.words.join ','

@@ -24,4 +22,4 @@
         tweet = beautify tweet, false
         tweet.counter = counter.inc() if counter

-        fn null, tweet
+        fn null, tweet


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -23,7 +23,7 @@
 enemy.I.velocity
 # => Point(-1, 0)

-player.update()
+.update()


Introduced shortcuts for left and right navigation.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -16,6 +16,8 @@
       'create new file'    : 'createNewFile'
       'collapse sidebar'   : 'collapseSidebar'
       'expand sidebar'     : 'expandSidebar'
+      'go to left tab'     : 'goToLeftTab'
+      'go to right tab'    : 'goToRightTab'
     keyBindings: [
       { command: 'split vertically',   binding: 'ctrl+alt+v', global: yes }
       { command: 'split horizontally', binding: 'ctrl+alt+h', global: yes }
@@ -23,6 +25,8 @@
       { command: 'create new file',    binding: 'ctrl+alt+n', global: yes }
       { command: 'collapse sidebar',   binding: 'ctrl+alt+c', global: yes }
       { command: 'expand sidebar',     binding: 'ctrl+alt+e', global: yes }
+      { command: 'go to left tab',     binding: 'ctrl+alt+[', global: yes }
+      { command: 'go to right tab',    binding: 'ctrl+alt+]', global: yes }
     ]

   constructor: (options = {}, data) ->
@@ -207,6 +211,18 @@

       @openFile file, contents

+  goToLeftTab: ->
+    index = @activeTabView.getActivePaneIndex()
+    return if index is 0
+
+    @activeTabView.showPaneByIndex index - 1
+
+  goToRightTab: ->
+    index = @activeTabView.getActivePaneIndex()
+    return if index is @activeTabView.length - 1
+
+    @activeTabView.showPaneByIndex index + 1
+
   forEachSubViewInIDEViews_: (callback = noop) ->
     for ideView in @ideViews
       for pane in ideView.tabView.panes


Collaboration: Don't resurrectSnapshot when machine is turned of.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -951,7 +951,7 @@

   resurrectSnapshot: ->

-    return  if @collaborationJustInitialized
+    return  if @collaborationJustInitialized or @fakeTabView

     snapshot = @mySnapshot.values()


minor: whitespace

minor: whitespace

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -525,12 +525,9 @@

   moveTabUp: -> @moveTab 'north'

-
   moveTabDown: -> @moveTab 'south'

-
   moveTabLeft: -> @moveTab 'west'
-

   moveTabRight: -> @moveTab 'east'

@@ -1103,7 +1100,6 @@

   watchParticipant: (nickname) -> @myWatchMap.set nickname, nickname
-

   unwatchParticipant: (nickname) -> @myWatchMap.delete nickname


parse entities in Tweets

parse entities in Tweets

--- scripts/preview/twitter.coffee
+++ scripts/preview/twitter.coffee
@@ -1,3 +1,6 @@
+escapeEntities = (str) ->
+  $('
').text(str).html()
+
 class TwitterScraper extends Preview.SummaryScraper
   Preview.register this

@@ -9,7 +12,7 @@

   scrape: ->
     if id = (@uri.fragment ? @uri.path)?.match(TWEET_PATTERN)?[1]
-      $.ajax "http://api.twitter.com/1/statuses/show/#{id}.json",
+      $.ajax "http://api.twitter.com/1/statuses/show/#{id}.json?include_entities=true",
         type: 'get', dataType: 'json', error: @pass
         # XXX REMOVE THIS LINE:
         cache: true
@@ -18,8 +21,47 @@
           preview = @createPreview
             title: "@#{user.screen_name}: #{tweet.text})"
             thumbnail: tweet.user.profile_image_url
-            snippet: tweet.text
+            snippet: @parseEntities(tweet)

           # TODO add more information
     else
       @pass()
+
+  parseEntities: (tweet) ->
+    # This function converts a tweet with "entity" metadata
+    # from plain text to linkified HTML.
+    #
+    # See the documentation here: http://dev.twitter.com/pages/tweet_entities
+    # Basically, add ?include_entities=true to your timeline call
+    #
+    # Copyright 2010, Wade Simmons
+    # Licensed under the MIT license
+    # http://wades.im/mons
+    if tweet.entities?
+      indexMap = { }
+      for item in tweet.entities.urls
+        indexMap[item.indices[0]] = [item.indices[1], (text) -> "#{escapeEntities text}" ]
+      for item in tweet.entities.hashtags
+        indexMap[item.indices[0]] = [item.indices[1], (text) -> "#{escapeEntities text}" ]
+      for item in tweet.entities.user_mentions
+        indexMap[item.indices[0]] = [item.indices[1], (text) -> "#{escapeEntities text}" ]
+
+      html = ''
+      lastIndex = 0
+
+      for index in [0...tweet.text.length]
+        if entry = indexMap[index]
+          [end, fn] = entry
+          if index > lastIndex
+            html += escapeEntities tweet.text.substring(lastIndex, index)
+          html += fn tweet.text.substring(index, end)
+          index = end - 1
+          lastIndex = end
+
+      if index > lastIndex
+        html += escapeEntities tweet.text.substring(lastIndex, i)
+
+      # span? more hax.
+      $('').html(html)
+    else
+      escapeEntities tweet.text


Activity: remove guideslinksview decleration inside from machine settings modal

--- client/Main/machinesettingsmodal.coffee
+++ client/Main/machinesettingsmodal.coffee
@@ -1,50 +1,3 @@
-class GuidesLinksView extends JView
-  constructor: (options = {}, data) ->
-
-    options.cssClass = KD.utils.curry 'guides-links', options.cssClass
-
-    super options, data
-
-    @createLinks()
-
-  createLinks: ->
-
-    @ssh = new CustomLinkView
-      cssClass : 'ssh'
-      title    : 'ssh'
-      href     : 'http://learn.koding.com/guides/ssh-into-your-vm/'
-      target   : '_blank'
-
-    @sudo = new CustomLinkView
-      cssClass : 'sudo'
-      title    : 'sudo'
-      href     : 'http://learn.koding.com/faq/what-is-my-sudo-password/'
-      target   : '_blank'
-
-    @mySQL = new CustomLinkView
-      cssClass : 'mysql'
-      title    : 'mySQL'
-      href     : 'http://learn.koding.com/guides/installing-mysql/'
-      target   : '_blank'
-
-    @nodeJS = new CustomLinkView
-      cssClass : 'nodejs'
-      title    : 'NodeJS'
-      href: 'http://learn.koding.com/guides/getting-started-nodejs/'
-      target   : '_blank'
-
-    @apache = new CustomLinkView
-      cssClass : 'apache'
-      title    : 'apache'
-      href     : 'http://learn.koding.com/categories/apache/'
-      target   : '_blank'
-
-
-  pistachio: ->
-    """
-    {{> @ssh}}, {{> @sudo}}, {{> @mySQL}}, {{> @nodeJS}}, {{> @apache}}
-    """
-
 class MachineSettingsModal extends KDModalViewWithForms

   { Running, Starting } = Machine.State


Dont expand tags by default.

Dont expand tags by default.

--- client/app/CommonViews/LinkViews.coffee
+++ client/app/CommonViews/LinkViews.coffee
@@ -73,7 +73,7 @@
 class TagLinkView extends LinkView

   constructor:(options = {}, data)->
-    options.expandable ?= yes
+    options.expandable ?= no
     if not options.expandable and data.title.length > 16
       options.tooltip =
         title     : data.title


Revert "Take 1 ms off the fromTime operator"

This reverts commit fe0ab48c9e442534edb7cb8ad7d058a07a39e68f.

--- assets/javascripts/custom_rx_operators.coffee
+++ assets/javascripts/custom_rx_operators.coffee
@@ -5,9 +5,7 @@
   timers = R.keys(timesAndValues)
     .map (relativeTime) -> [parseInt(relativeTime), timesAndValues[relativeTime]]
     .map ([time, value]) ->
-      # 1 is substracted at the moment to not distract users
-      # with a 1ms delay due to test scheduler
-      Rx.Observable.timer(Math.max(time - 1, 0), scheduler).map R.I(value)
+      Rx.Observable.timer(time, scheduler).map R.I(value)

   Rx.Observable.merge(timers)


minor

minor

--- client/views/ontap/ontap.coffee
+++ client/views/ontap/ontap.coffee
@@ -8,9 +8,8 @@
 Template.ontap.events
     "click button.edit": (evt) ->
         evt.preventDefault()
-        id = $(evt.target).attr('id')
         href = $(evt.target).attr('href')
-        Router.go(href+'/'+id)
+        Router.go(href)

     "click button.fullscreen": (evt) ->
         evt.preventDefault()


Inspect the right object

Inspect the right object

--- spec/SetElementTop.coffee
+++ spec/SetElementTop.coffee
@@ -26,7 +26,7 @@
       element.send el
       top.send 10
       setTimeout ->
-        chai.expect(element.style.position).to.equal 'absolute'
-        chai.expect(element.style.top).to.equal '10px'
+        chai.expect(el.style.position).to.equal 'absolute'
+        chai.expect(el.style.top).to.equal '10px'
         done()
       , 1


fix merge leftovers

fix merge leftovers

--- spec/event-helpers.coffee
+++ spec/event-helpers.coffee
@@ -104,8 +104,4 @@
   Object.defineProperty(dragEndEvent, 'currentTarget', value: dragged)
   Object.defineProperty(dragEndEvent, 'dataTransfer', value: dataTransfer)

-<<<<<<< HEAD
   [dragStartEvent, buildElementPositionalDragEvents(target, dataTransfer, currentTargetSelector), dragEndEvent]
-=======
-  dropEvent
->>>>>>> refactor event helpers for multiple targets in drag


Better coffee-script error reporting

Better coffee-script error reporting

--- assets/js/lib/compilers.coffee
+++ assets/js/lib/compilers.coffee
@@ -1,116 +1,134 @@
-(@plunker ||= {}).compilers =
-  coffee: (code, cb) ->
-    $script "/js/compilers/coffee-script.js", "compiler-coffee"
-    $script.ready "compiler-coffee", ->
-      try
-        compiled = CoffeeScript.compile(code, bare: true)
-        return cb null,
-          type: "code"
-          body: compiled
-          lang: "javascript"
-          title: "Compiled coffee-script"
-      catch error
-        return cb error
-    return
+((plunker) ->
+
+  buildErrorReport = (errors) ->
+    $wrap = $("

")
+    $report = $("

Errors:

")
+      .addClass("jshint-report")
+      .addClass("alert")
+      .addClass("alert-block")
+      .addClass("alert-error")
+      .appendTo($wrap)
+    $errors = $("

").addClass("jshint-errors").appendTo($report)
+
+    _.each errors, ({line, reason, evidence, character}) ->
+      $li = $("

").appendTo($errors)
+      $error = $("

").appendTo($li)
+      $line = $("Line #{line}")
+        .attr("href", "javascript:void(0)")
+        .attr("data-line", line)
+        .attr("data-char", character)
+        .addClass("lineno")
+        .appendTo($error)
+      $error.append(": ")
+      $code = $("#{evidence}").appendTo($error)
+      $reason = $("
#{reason}
").appendTo($li)

-  markdown: (code, cb) ->
-    $script "/js/compilers/showdown.js", "compiler-markdown"
-    $script.ready "compiler-markdown", ->
-      converter = new Showdown.converter()
-      try
-        compiled = converter.makeHtml(code)
-        return cb null,
-          type: "html"
-          body: compiled
-          title: "Rendered markdown"
-      catch error
-        return cb error
-      return
+    html = $wrap.html()
+    $wrap.remove()

-  javascript: (code, cb) ->
-    $script "/js/compilers/jshint.js", "compiler-jshint"
-    $script.ready "compiler-jshint", ->
-      try
-        valid = JSHINT code,
-          browser: false
-          devel: true
-
-        if valid
-          $script "/js/compilers/beautify.js", "compiler-javascript"
-          $script.ready "compiler-javascript", ->
-            compiled = js_beautify code,
-              indent_size: 2
-              intent_char: " "
-            return cb null,
-              type: "code"
-              body: compiled
-              lang: "javascript"
-              title: "Beautified js"
-        else
-          $wrap = $("

")
-          $report = $("

Errors:

")
-            .addClass("jshint-report")
-            .addClass("alert")
-            .addClass("alert-block")
-            .addClass("alert-error")
-            .appendTo($wrap)
-          $errors = $("

").addClass("jshint-errors").appendTo($report)
-
-          _.each JSHINT.errors, ({line, reason, evidence, character}) ->
-            $li = $("

").appendTo($errors)
-            $error = $("

").appendTo($li)
-            $line = $("Line #{line}")
-              .attr("href", "javascript:void(0)")
-              .attr("data-line", line)
-              .attr("data-char", character)
-              .addClass("lineno")
-              .appendTo($error)
-            $error.append(": ")
-            $code = $("#{evidence}").appendTo($error)
-            $reason = $("
#{reason}
").appendTo($li)
+    html
+
+  plunker.compilers =
+    coffee: (code, cb) ->
+      $script "/js/compilers/coffee-script.js", "compiler-coffee"
+      $script.ready "compiler-coffee", ->
+        try
+          compiled = CoffeeScript.compile(code, bare: true)
+          return cb null,
+            type: "code"
+            body: compiled
+            lang: "javascript"
+            title: "Compiled coffee-script"
+        catch error
+          matches = error.message.match /^(.+) on line ([0-9]+)\: (.+)$/i
+          lines = code.split(/\r?\n/)
+          err =
+            line: parseInt(matches[2], 10)
+            reason: matches[3]

-          html = $wrap.html()
-          $wrap.remove()
-
+          err.evidence = $.trim(lines[err.line - 1])
+          err.character = lines[err.line - 1].indexOf(err.evidence)
+
           return cb null,
             type: "html"
-            body: html
-            title: "jsLint"
-
-      catch error
-        return cb error
-      return
-
-  html: (code, cb) ->
-    $script "/js/compilers/beautify-html.js", "compiler-html"
-    $script "/js/compilers/beautify.js", "compiler-javascript"
-    $script "/js/compilers/beautify-css.js", "compiler-css"
-    $script.ready ["compiler-html", "compiler-javascript", "compiler-css"], ->
-      try
-        compiled = style_html code,
-          indent_size: 2
-          intent_char: " "
-        return cb null,
-          type: "code"
-          body: compiled
-          lang: "html"
-          title: "Beautified html"
-      catch error
-        return cb error
-      return
+            body: buildErrorReport([err])
+            title: "Coffee errors"
+        return

-  css: (code, cb) ->
-    $script "/js/compilers/beautify-css.js", "compiler-css"
-    $script.ready "compiler-css", ->
-      try
-        compiled = css_beautify code,
-          indent_size: 2
-          intent_char: " "
-        return cb null,
-          type: "code"
-          body: compiled
-          lang: "css"
-          title: "Beautified css"
-      catch error
-        return cb error
-      return+    markdown: (code, cb) ->
+      $script "/js/compilers/showdown.js", "compiler-markdown"
+      $script.ready "compiler-markdown", ->
+        converter = new Showdown.converter()
+        try
+          compiled = converter.makeHtml(code)
+          return cb null,
+            type: "html"
+            body: compiled
+            title: "Rendered markdown"
+        catch error
+          return cb error
+        return
+
+    javascript: (code, cb) ->
+      $script "/js/compilers/jshint.js", "compiler-jshint"
+      $script.ready "compiler-jshint", ->
+        try
+          valid = JSHINT code,
+            browser: false
+            devel: true
+
+          if valid
+            $script "/js/compilers/beautify.js", "compiler-javascript"
+            $script.ready "compiler-javascript", ->
+              compiled = js_beautify code,
+                indent_size: 2
+                intent_char: " "
+              return cb null,
+                type: "code"
+                body: compiled
+                lang: "javascript"
+                title: "Beautified js"
+          else
+            return cb null,
+              type: "html"
+              body: buildErrorReport(JSHINT.errors)
+              title: "jsLint"
+
+        catch error
+          return cb error
+        return
+
+    html: (code, cb) ->
+      $script "/js/compilers/beautify-html.js", "compiler-html"
+      $script "/js/compilers/beautify.js", "compiler-javascript"
+      $script "/js/compilers/beautify-css.js", "compiler-css"
+      $script.ready ["compiler-html", "compiler-javascript", "compiler-css"], ->
+        try
+          compiled = style_html code,
+            indent_size: 2
+            intent_char: " "
+          return cb null,
+            type: "code"
+            body: compiled
+            lang: "html"
+            title: "Beautified html"
+        catch error
+          return cb error
+        return
+
+    css: (code, cb) ->
+      $script "/js/compilers/beautify-css.js", "compiler-css"
+      $script.ready "compiler-css", ->
+        try
+          compiled = css_beautify code,
+            indent_size: 2
+            intent_char: " "
+          return cb null,
+            type: "code"
+            body: compiled
+            lang: "css"
+            title: "Beautified css"
+        catch error
+          return cb error
+        return
+)(@plunker ||= {})


update refilter method

update refilter method

--- lib/client/javascripts/nali/collection.js.coffee
+++ lib/client/javascripts/nali/collection.js.coffee
@@ -15,7 +15,9 @@
     @

   refilter: ->
-    @model.each ( model ) => @add model if model.isCorrect( @filters ) and not ( model in @ )
+    @model.each ( model ) =>
+      if isCorrect = model.isCorrect( @filters ) and not ( model in @ ) then @add model
+      else if not isCorrect and model in @ then @remove model
     @

   new: ( model, filters ) ->


comment

comment

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -14,6 +14,8 @@

         return if dependencies[0] is '' then [] else dependencies

+    # find the first instance or factory of the service
+    # specified by `id` in `containers`
     find: (containers, id) ->
         len = containers.length
         i = 0


Added exclude option

Added exclude option

--- server/modfetch/fetcher.coffee
+++ server/modfetch/fetcher.coffee
@@ -54,6 +54,7 @@
   playable: false
   requirements: {}
   spreadvideo: null
+  exclude: []
 _.matches = (attrs) ->
   (obj) ->
     return _.isEmpty(attrs)  unless obj?
@@ -98,7 +99,10 @@
     fs.unlinkSync stagem+'info.json'
     addonInfo = generateAddonInfo fetch.info, fetch.steamid
     fs.writeFileSync stagem+"addoninfo.txt", addonInfo
-    console.log addonInfo
+    exclude = fetch.info.exclude
+    if exclude?
+      for dir in exclude
+        rmdir stagem+dir
     #now bundle
     log.info "Creating server bundle #{servname}..."
     clientPath = stage+clientname


remove path from endpoint

remove path from endpoint

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -109,7 +109,7 @@

   expectedOrigin: ->
     endpoint = @get('endpoint')
-    if endpoint[0] == '/' then @receivingEnd else endpoint
+    if endpoint[0] == '/' then @receivingEnd else endpoint.match(/^https?:\/\/[^\/]*/)[0]

   sendToApp: (name) ->
     # TODO: this is an ugly solution, we need to do one of 2 things:


added prompt once site is generated

added prompt once site is generated

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -72,6 +72,7 @@
       bundler: cmd: 'bundle install'
       jekyll: cmd: 'jekyll build --trace'
       new_post: cmd: 'bash _helper/new-post.sh'
+      status: cmd: "clear && echo the site is now accessible at http://localhost:<%= connect.server.options.port %>"
     watch:
       options: livereload: false
       source:
@@ -97,8 +98,10 @@
     'exec:jekyll'
   ]
   grunt.registerTask 'serve', [
+
     'build'
     'connect:server'
+    'exec:status'
     'watch'
   ]
   grunt.registerTask 'test', [ 'csslint' ]


Added icon to manifest

Added icon to manifest

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,7 +29,8 @@
                     hash: true

                 src: [
-                    #'index.html'
+                    'index.html'
+                    'img/icon_pruhledna.png'
                     'js/*.js'
                     'css/*.css'
                     'snd/*'


add `img/`

add `img/`

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -30,7 +30,7 @@
             jshint:
                 files: ['js/*.js']
                 tasks: ['jshint']
-
+
             sass:
                 files: ['css/source/theme.scss']
                 tasks: ['sass']
@@ -40,7 +40,7 @@
             theme:
                 files:
                     'css/theme.css': 'css/source/theme.scss'
-
+
         connect:

             livereload:
@@ -79,7 +79,8 @@
                         'slides/**'
                         'bower_components/**'
                         'js/**'
-                        'css/*.css'
+                        'css/*.css',
+                        'img/**'
                     ]
                     dest: 'dist/'
                 },{
@@ -89,7 +90,7 @@
                     filter: 'isFile'
                 }]

-
+
         buildcontrol:

             options:
@@ -101,7 +102,7 @@
                 options:
                     remote: 'git@github.com:mischah/js-tooling-slides.git'
                     branch: 'gh-pages'
-
+

     # Load all grunt tasks.
@@ -151,13 +152,13 @@
             'copy'
         ]

-
+
     grunt.registerTask 'deploy',
         'Deploy to Github Pages', [
             'dist'
             'buildcontrol'
         ]
-
+

     # Define default task.
     grunt.registerTask 'default', [


on release:nobump there is no file to commit, tags only

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -47,7 +47,7 @@
   grunt.registerTask 'release:patch', [ 'release:prep', 'semver:bump:patch', 'release:commit' ]
   grunt.registerTask 'release:minor', [ 'release:prep', 'semver:bump:minor', 'release:commit' ]
   grunt.registerTask 'release:major', [ 'release:prep', 'semver:bump:major', 'release:commit' ]
-  grunt.registerTask 'release:nobump', [ 'release:prep', 'release:commit' ]
+  grunt.registerTask 'release:nobump', [ 'release:prep', 'git:tag:release' ]

   grunt.registerTask 'release:push', ['git:push', 'git:pushTags' ]
   grunt.registerTask 'release:publish', ['release:push', 'npm:publish' ]


Lint all .coffee files under src/

Lint all .coffee files under src/

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,7 +15,7 @@
         max_line_length:
           level: 'ignore'

-      src: ['src/*.coffee']
+      src: ['src/**/*.coffee']
       test: ['spec/*.coffee']

     shell:


cleanup gruntfile

cleanup gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,34 +6,34 @@
         compass:
             example:
                 options:
-                    sassDir: './example'
-                    cssDir: './example'
+                    sassDir: 'example'
+                    cssDir: 'example'
                     outputStyle: 'compressed'
             src:
                 options:
-                    sassDir: './src'
-                    cssDir: './dist'
+                    sassDir: 'src'
+                    cssDir: 'dist'
                     outputStyle: 'compressed'

         coffee:
             source:
                 files:
-                    './dist/angular-form-builder.js': ['./src/*.coffee']
+                    'dist/angular-form-builder.js': ['src/*.coffee']
             components:
                 files:
-                    './dist/angular-form-builder-components.js': ['./components/*.coffee']
+                    'dist/angular-form-builder-components.js': ['components/*.coffee']
             demo:
                 files:
-                    './example/demo.js': './example/demo.coffee'
+                    'example/demo.js': 'example/demo.coffee'

         watch:
             compass:
-                files: ['./example/*.scss', './src/*.scss']
+                files: ['example/*.scss', 'src/*.scss']
                 tasks: ['compass']
                 options:
                     spawn: no
             coffee:
-                files: ['./src/*.coffee', './components/*.coffee', './example/*.coffee']
+                files: ['src/*.coffee', 'components/*.coffee', 'example/*.coffee']
                 tasks: ['coffee']
                 options:
                     spawn: no
@@ -48,9 +48,9 @@

         karma:
             min:
-                configFile: './test/karma-min.config.coffee'
+                configFile: 'test/karma-min.config.coffee'
             source:
-                configFile: './test/karma.config.coffee'
+                configFile: 'test/karma.config.coffee'

     # -----------------------------------
     # register task


Removed update-s3 out of gh_pages task

Removed update-s3 out of gh_pages task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -116,7 +116,7 @@
   grunt.registerTask 'build', ['coffee', 'concat', 'uglify', 'cssmin']
   grunt.registerTask 'master-s3', ['build', 'zip:chosen', 's3:master', 'clean:chosen_zip']
   grunt.registerTask 'update-s3', ['build', 'zip:chosen', 'package_jquery', 'dom_munger:download_links', 's3:master', 's3:latest_version', 'clean:chosen_zip']
-  grunt.registerTask 'gh_pages', ['copy:dist', 'build_gh_pages:gh_pages', 'update-s3']
+  grunt.registerTask 'gh_pages', ['copy:dist', 'build_gh_pages:gh_pages']

   grunt.registerTask 'package_jquery', 'Generate a jquery.json manifest file from package.json', () ->
     src = "package.json"


Simplify Grunt CoffeeScript compile  syntax

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -4,11 +4,11 @@
   grunt.initConfig
     coffee:
       compile:
-        files:
-          'lib/shuss-cli.js': 'src/shuss-cli.coffee'
-          'lib/shuss-config.js': 'src/shuss-config.coffee'
-          'lib/shuss-logger.js': 'src/shuss-logger.coffee'
-          'lib/shuss-plugin-loader.js': 'src/shuss-plugin-loader.coffee'
-          'lib/shuss-server.js': 'src/shuss-server.coffee'
+        expand: true
+        flatten: true
+        cwd: 'src/'
+        src: ['*.coffee']
+        dest: 'lib/'
+        ext: '.js'

   grunt.registerTask 'default', ['coffee:compile']