update patterns

update patterns

--- grammars/inno-setup.cson
+++ grammars/inno-setup.cson
@@ -3,46 +3,51 @@
   "isl"
 ]
 name: "Inno Setup"
-comment: "https://github.com/idleberg/atom-language-innosetup"
+comment: "https://github.com/idleberg/sublime-innosetup"
 patterns: [
   {
-    captures:
+    begin: "^(\\[Code\\])$"
+    beginCaptures:
       "1":
-        name: "punctuation.definition.comment.inno"
-    match: "^\\s*(;|#).*$"
-    name: "comment.line.semicolon.inno"
+        name: "entity.name.section.inno"
+    end: "^(?=\\[(Components|CustomMessages|Dirs|Files|Icons|INI|InstallDelete|LangOptions|Languages|Messages|Registry|Run|Setup|Tasks|Types|UninstallDelete|UninstallRun)\\])$"
+    endCaptures:
+      "1":
+        name: "entity.name.section.inno"
+    name: "source.pascal.embedded.inno"
+    patterns: [
+      {
+        include: "source.pascal.inno"
+      }
+    ]
   }
   {
-    match: "\\[[\\w]+\\]"
+    match: "^\\s*(AllowCancelDuringInstall|AllowNetworkDrive|AllowNoIcons|AllowRootDirectory|AllowUNCPath|AlwaysCreateUninstallIcon|AlwaysRestart|AlwaysShowComponentsList|AlwaysShowDirOnReadyPage|AlwaysShowGroupOnReadyPage|AlwaysUsePersonalGroup|AppComments|AppContact|AppCopyright|AppendDefaultDirName|AppendDefaultGroupName|AppId|AppModifyPath|AppMutex|AppName|AppPublisher|AppPublisherURL|AppReadmeFile|AppSupportPhone|AppSupportURL|AppUpdatesURL|AppVerName|AppVersion|ArchitecturesAllowed|ArchitecturesInstallIn64BitMode|BackColor|BackColor2|BackColorDirection|BackSolid|ChangesAssociations|ChangesEnvironment|CloseApplications|CloseApplicationsFilter|Compression|CompressionThreads|CopyrightFontName|CopyrightFontSize|CreateAppDir|CreateUninstallRegKey|DefaultDialogFontName|DefaultDirName|DefaultGroupName|DefaultUserInfoName|DefaultUserInfoOrg|DefaultUserInfoSerial|DialogFontName|DialogFontSize|DirExistsWarning|DisableDirPage|DisableFinishedPage|DisableProgramGroupPage|DisableReadyMemo|DisableReadyPage|DisableStartupPrompt|DisableWelcomePage|DiskClusterSize|DiskSliceSize|DiskSpanning|EnableDirDoesntExistWarning|Encryption|ExtraDiskSpaceRequired|FlatComponentsList|InfoAfterFile|InfoBeforeFile|InternalCompressLevel|LanguageCodePage|LanguageDetectionMethod|LanguageID|LanguageName|LicenseFile|LZMAAlgorithm|LZMABlockSize|LZMADictionarySize|LZMAMatchFinder|LZMANumBlockThreads|LZMANumFastBytes|LZMAUseSeparateProcess|MergeDuplicateFiles|MinVersion|OnlyBelowVersion|Output|OutputBaseFilename|OutputDir|OutputManifestFile|Password|PrivilegesRequired|ReserveBytes|RestartApplications|RestartIfNeededByRun|RightToLeft|SetupIconFile|SetupLogging|ShowComponentSizes|ShowLanguageDialog|ShowTasksTreeLines|ShowUndisplayableLanguages|SignedUninstaller|SignedUninstallerDir|SignTool|SignToolRetryCount|SlicesPerDisk|SolidCompression|SourceDir|TerminalServicesAware|TimeStampRounding|TimeStampsInUTC|TitleFontName|TitleFontSize|TouchDate|TouchTime|Uninstallable|UninstallDisplayIcon|UninstallDisplayName|UninstallDisplaySize|UninstallFilesDir|UninstallLogMode|UninstallRestartComputer|UpdateUninstallLogAppName|UsePreviousAppDir|UsePreviousGroup|UsePreviousLanguage|UsePreviousSetupType|UsePreviousTasks|UsePreviousUserInfo|UserInfoPage|UseSetupLdr|VersionInfoCompany|VersionInfoCopyright|VersionInfoDescription|VersionInfoProductName|VersionInfoProductTextVersion|VersionInfoProductVersion|VersionInfoTextVersion|VersionInfoVersion|WelcomeFontName|WelcomeFontSize|WindowResizable|WindowShowCaption|WindowStartMaximized|WindowVisible|WizardImageBackColor|WizardImageFile|WizardImageStretch|WizardSmallImageFile)(?=\\s*=)"
+    name: "keyword.inno"
+  }
+  {
+    match: "^\\s*#(append|define|dim|elif|else|emit|endif|endsub|error|expr|file|for|if|ifn?def|ifn?exist|include|insert|pragma|preproc|redim|sub|undef)\\b"
+    name: "keyword.other.inno"
+  }
+  {
+    match: "\\b(AfterInstall|AfterMyProgInstall|BeforeInstall|BeforeMyProgInstall|Check|Components|Description|DestDir|DestName|Filename|Flags|Languages|Name|Parameters|Root|Source|StatusMsg|Subkey|Type|Types|ValueData|ValueName|ValueType|WorkingDir)(?=\\s*:)"
+    name: "keyword.other.inno"
+  }
+  {
+    match: "\\b(BeveledLabel|MyAppName|MyAppVerName|MyDescription)\\b"
+    name: "keyword.other.inno"
+  }
+  {
+    match: "^\\[(Components|CustomMessages|Dirs|Files|Icons|INI|InstallDelete|LangOptions|Languages|Messages|Registry|Run|Setup|Tasks|Types|UninstallDelete|UninstallRun)\\]$"
     name: "entity.name.section.inno"
   }
   {
-    match: "{(#)?[\\w]+}"
-    name: "variable.constant.inno"
+    match: "\\b(DisableAppendDir|DontMergeDuplicateFiles|MessagesFile|UninstallIconFile|UninstallIconName|UninstallStyle|WizardSmallImageBackColor|WizardStyle)\\b"
+    name: "constant.language.inno"
   }
   {
-    match: "%[\\w]"
-    name: "variable.inno"
-  }
-  {
-    match: "\\b(AllowCancelDuringInstall|AllowNetworkDrive|AllowNoIcons|AllowRootDirectory|AllowUNCPath|AlwaysCreateUninstallIcon|AlwaysRestart|AlwaysShowComponentsList|AlwaysShowDirOnReadyPage|AlwaysShowGroupOnReadyPage|AlwaysUsePersonalGroup|AppComments|AppContact|AppCopyright|AppendDefaultDirName|AppendDefaultGroupName|AppId|AppModifyPath|AppMutex|AppName|AppPublisher|AppPublisherURL|AppReadmeFile|AppSupportPhone|AppSupportURL|AppUpdatesURL|AppVerName|AppVersion|ArchitecturesAllowed|ArchitecturesInstallIn64BitMode|BackColor|BackColor2|BackColorDirection|BackSolid|ChangesAssociations|ChangesEnvironment|CloseApplications|CloseApplicationsFilter|Compression|CompressionThreads|CopyrightFontName|CopyrightFontSize|CreateAppDir|CreateUninstallRegKey|DefaultDialogFontName|DefaultDirName|DefaultGroupName|DefaultUserInfoName|DefaultUserInfoOrg|DefaultUserInfoSerial|DialogFontName|DialogFontSize|DirExistsWarning|DisableDirPage|DisableFinishedPage|DisableProgramGroupPage|DisableReadyMemo|DisableReadyPage|DisableStartupPrompt|DisableWelcomePage|DiskClusterSize|DiskSliceSize|DiskSpanning|EnableDirDoesntExistWarning|Encryption|ExtraDiskSpaceRequired|FlatComponentsList|InfoAfterFile|InfoBeforeFile|InternalCompressLevel|LanguageCodePage|LanguageDetectionMethod|LanguageID|LanguageName|LicenseFile|LZMAAlgorithm|LZMABlockSize|LZMADictionarySize|LZMAMatchFinder|LZMANumBlockThreads|LZMANumFastBytes|LZMAUseSeparateProcess|MergeDuplicateFiles|MinVersion|OnlyBelowVersion|Output|OutputBaseFilename|OutputDir|OutputManifestFile|Password|PrivilegesRequired|ReserveBytes|RestartApplications|RestartIfNeededByRun|RightToLeft|SetupIconFile|SetupLogging|ShowComponentSizes|ShowLanguageDialog|ShowTasksTreeLines|ShowUndisplayableLanguages|SignedUninstaller|SignedUninstallerDir|SignTool|SignToolRetryCount|SlicesPerDisk|SolidCompression|SourceDir|TerminalServicesAware|TimeStampRounding|TimeStampsInUTC|TitleFontName|TitleFontSize|TouchDate|TouchTime|Uninstallable|UninstallDisplayIcon|UninstallDisplayName|UninstallDisplaySize|UninstallFilesDir|UninstallLogMode|UninstallRestartComputer|UpdateUninstallLogAppName|UsePreviousAppDir|UsePreviousGroup|UsePreviousLanguage|UsePreviousSetupType|UsePreviousTasks|UsePreviousUserInfo|UserInfoPage|UseSetupLdr|VersionInfoCompany|VersionInfoCopyright|VersionInfoDescription|VersionInfoProductName|VersionInfoProductTextVersion|VersionInfoProductVersion|VersionInfoTextVersion|VersionInfoVersion|WelcomeFontName|WelcomeFontSize|WindowResizable|WindowShowCaption|WindowStartMaximized|WindowVisible|WizardImageBackColor|WizardImageFile|WizardImageStretch|WizardSmallImageFile)\\b"
-    name: "keyword.command.inno"
-  }
-  {
-    match: "\\b(BeveledLabel|MyAppName|MyAppVerName|MyDescription)\\b"
-    name: "keyword.messages.inno"
-  }
-  {
-    match: "\\b(DisableAppendDir|DontMergeDuplicateFiles|MessagesFile|UninstallIconFile|UninstallIconName|UninstallStyle|WizardSmallImageBackColor|WizardStyle)\\b"
-    name: "string.obsolete.inno"
-  }
-  {
-    match: "\\b(AfterInstall|AfterMyProgInstall|BeforeInstall|BeforeMyProgInstall|Check|Components|Description|DestDir|DestName|Filename|Flags|Languages|Name|Parameters|Root|Source|StatusMsg|Subkey|Type|Types|ValueData|ValueName|ValueType|WorkingDir)\\b:"
-    name: "constant.equals.inno"
-  }
-  {
-    match: "(:)?(\\+|=)"
-    name: "constant.character.inno"
+    match: "="
+    name: "keyword.operator.comparison.inno"
   }
   {
     match: "\\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\\.[0-9]+)?))\\b"
@@ -50,11 +55,55 @@
   }
   {
     match: "\\b(32bit|64bit|admin|allowunsafefiles|append|auto|binary|bzip|checkablealone|checkedonce|clAqua|clBlack|clBlue|clFuchsia|clGray|clGreen|clLime|clMaroon|clNavy|clOlive|closeonexit|clPurple|clRed|clSilver|clTeal|clWhite|clYellow|compact|comparetimestamp|compiler|confirmoverwrite|createallsubdirs|createkeyifdoesntexist|createonlyiffileexists|createvalueifdoesntexist|current|custom|deleteafterinstall|deletekey|deletevalue|desktopicon|dirifempty|disablenouninstallwarning|dontcloseonexit|dontcopy|dontcreatekey|dontinheritcheck|dontverifychecksum|dword|excludefromshowinnewinstall|exclusive|expandsz|external|fast|files|filesandordirs|fixed|foldershortcut|fontisnttruetype|full|gacinstall|help|hidewizard|ia64|ignoreversion|iscustom|isreadme|lefttoright|locale|lowest|lzma|lzma2|main|max|modify|multisz|new|no|nocompression|noencryption|noerror|none|noregerror|normal|nowait|onlyifdoesntexist|overwrite|overwritereadonly|postinstall|poweruser|preservestringtype|preventpinning|program|promptifolder|qword|read|readexec|readme|recursesubdirs|regserver|regtypelib|replacesameversion|restart|restartreplace|runascurrentuser|runasoriginaluser|runhidden|runminimized|setntfscompression|sharedfile|shellexec|skipifdoesntexist|skipifnotsilent|skipifsilent|skipifsourcedoesntexist|solidbreak|sortfilesbyextension|sortfilesbyname|string|toptobottom|touch|uilanguage|ultra|unchecked|uninsalwaysuninstall|uninsclearvalue|uninsdeleteentry|uninsdeletekey|uninsdeletekeyifempty|uninsdeletesection|uninsdeletesectionifempty|uninsdeletevalue|uninsneveruninstall|uninsnosharedfileprompt|uninsremovereadonly|uninsrestartdelete|unsetntfscompression|useapppaths|waituntilidle|waituntilterminated|x64|x86|yes|zip)\\b"
-    name: "string.flags.inno"
+    name: "constant.language.inno"
   }
   {
     match: "\\b(aa|ab|ae|af|ak|am|an|ar|as|av|ay|az|ba|be|bg|bh|bi|bm|bn|bo|br|bs|ca|ce|ch|co|cr|cs|cu|cv|cy|da|de|dv|dz|ee|el|en|eo|es|et|eu|fa|ff|fi|fj|fo|fr|fy|ga|gd|gl|gn|gu|gv|ha|he|hi|ho|hr|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|io|is|it|iu|ja|jv|ka|kg|ki|kj|kk|kl|km|kn|ko|kr|ks|ku|kv|kw|ky|la|lb|lg|li|ln|lo|lt|lu|lv|mg|mh|mi|mk|mk|ml|mn|mr|ms|mt|my|na|nb|nd|ne|ng|nl|nn|no|nr|nv|ny|oc|oj|om|or|os|pa|pi|pl|ps|pt|qt|qu|rg|rm|rn|ro|ru|rw|sa|sc|sd|se|sg|si|sk|sl|sm|sn|so|sq|sr|ss|st|su|sv|sw|ta|te|tg|th|ti|tk|tl|tn|to|tr|ts|tt|tw|ty|ug|uk|ur|uz|ve|vi|vo|wa|wo|xh|yi|yo|zh)(?:\\.\\w)?\\b"
-    name: "variable.language.inno"
+    name: "constant.language.inno"
+  }
+  {
+    match: "{(app|cf32|cf64|cf|cmd|commonappdata|commondocs|computername|dao|dotnet11|dotnet2032|dotnet2064|dotnet20|dotnet4032|dotnet4064|dotnet40|fonts|groupname|group|hwnd|language|localappdata|log|pf32|pf64|pf|sd|sendto|srcexe|src|sysuserinfoname|sysuserinfoorg|syswow64|sys|tmp|uninstallexe|usercf|userinfoname|userinfoorg|userinfoserial|username|userpf|win|wizardhwnd)}"
+    name: "constant.other.inno"
+  }
+  {
+    match: "%\\w+"
+    name: "variable.other.inno"
+  }
+  {
+    begin: ":\\s*\""
+    beginCaptures:
+      "0":
+        name: "punctuation.definition.string.begin.inno"
+    end: "\"{1}\\s*(;|$\\n?)"
+    endCaptures:
+      "0":
+        name: "punctuation.definition.string.end.inno"
+    name: "string.quoted.back.inno"
+    patterns: [
+      {
+        match: "{(app|cf32|cf64|cf|cmd|commonappdata|commondocs|computername|dao|dotnet11|dotnet2032|dotnet2064|dotnet20|dotnet4032|dotnet4064|dotnet40|fonts|groupname|group|hwnd|language|localappdata|log|pf32|pf64|pf|sd|sendto|srcexe|src|sysuserinfoname|sysuserinfoorg|syswow64|sys|tmp|uninstallexe|usercf|userinfoname|userinfoorg|userinfoserial|username|userpf|win|wizardhwnd)}"
+        name: "constant.other.inno"
+      }
+      {
+        match: "%\\w+"
+        name: "variable.other.inno"
+      }
+      {
+        match: "\"{2}"
+        name: "constant.character.escape.inno"
+      }
+      {
+        match: "\"{1}"
+        name: "invalid.illegal.inno"
+      }
+    ]
+  }
+  {
+    captures:
+      "1":
+        name: "punctuation.definition.comment.inno"
+    match: "^\\s*;.*$"
+    name: "comment.line.semicolon.inno"
   }
 ]
 scopeName: "source.inno"


Assign auth token in AuthenticatedRoute

Assign auth token in AuthenticatedRoute

--- assets/javascripts/routes/authenticated_route.js.coffee
+++ assets/javascripts/routes/authenticated_route.js.coffee
@@ -12,6 +12,7 @@
           lastName: response.user.last_name,
           role: response.user.role
           email: response.user.email
+          authToken: response.user.auth_token
           color: App.paintBox.getColor()

         if @store.recordIsLoaded("user", userAttributes.id)


switch to using image urls that come with search results

--- models/search_result.coffee
+++ models/search_result.coffee
@@ -46,7 +46,7 @@
     text.replace new RegExp("(#{term})", 'ig'), '$1'

   imageUrl: ->
-    "#{sd.APP_URL}/search/image/#{@get('model')}/#{@get('id')}"
+    @get('image_url')

   updateForFair: (fair) ->
     if @get('display_model') == 'Show'


adding App#fetch, which is alias of App#fetcher.fetch

--- shared/app.coffee
+++ shared/app.coffee
@@ -24,6 +24,10 @@

   postInitialize: noop

+  # @shared
+  fetch: (spec, callback) ->
+    @fetcher.fetch(spec, callback)
+
   # @client
   bootstrapData: (modelMap) ->
     results = {}


Add some error handling for missing index.html and don't return 500s on 404s

--- src/web-server.coffee
+++ src/web-server.coffee
@@ -1,6 +1,6 @@
 connect = require("connect")
 FileSystem = require "fs"
-{resolve} = require "path"
+{resolve,join} = require "path"
 async = require "async"

 isFile = (path, callback) ->
@@ -16,7 +16,7 @@
 Ax = require "ax"
 log = new Ax level: "info", stream: process.stdout

-webPath = (resolve __dirname, "..", "build", "web")
+webPath = (resolve "build", "web")

 logger = (request,response,next) ->
   end = response.end
@@ -25,6 +25,16 @@
     log.info "#{response.statusCode} #{request.method} #{request.url}"
   next()

+streamResponse = (response,path) ->
+  stream = FileSystem.createReadStream( path )
+  stream.pipe( response )
+  stream.on "error", (error) ->
+    log.error "Error: unable to stream response from #{path}"
+    log.error if error.stack? then error.stack else error.message
+    response.statusCode = 500
+    response.end()
+
+
 class Server

   constructor: (options) ->
@@ -42,28 +52,29 @@
     # for HTML or */* ... ex: /foo/bar to build/foo/bar.html
     @app.use (request,response,next) ->
       accept = request.headers["accept"]
-      if ( request.method == "GET" ) and
-      ( accept.match( /html/ ) or accept == "*/*" )
-        path = resolve( webPath, request.url[1..] )
+      console.log "ACCEPT", accept
+      if ( request.method == "GET" ) and accept.match( /text\/html/ )
+        path = join( webPath, request.url[1..] )
         async.detect [
-          resolve( path, "index.html" )
+          join( path, "index.html" )
           "#{path}.html"
           ], isFile, (path) ->
             # If we found a file, then stream it in the response;
             # otherwise, just return /index.html and let the client-side
             # app figure out what to do with the path
-            if path? and ( stream = FileSystem.createReadStream( path ) )?
-              stream.pipe( response )
+            if path?
+              streamResponse( response, path )
               response.setHeader("content-type","text/html")
             else
-              path = resolve( webPath, "index.html" )
-              stream = FileSystem.createReadStream( path )
-              if stream?
-                stream.pipe( response )
-                response.setHeader("content-type","text/html")
-              else
-                log.error "Unable to read from /index.html"
-
+              path = join( webPath, "index.html" )
+              FileSystem.exists path, (exists) ->
+                if exists?
+                  streamResponse( response, path )
+                  response.setHeader("content-type","text/html")
+                else
+                  log.error "Error: Missing /index.html"
+                  response.statusCode = 500
+                  response.end()
       else
         next()


Removed triggerPush from package object as it was unneccessary

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -104,12 +104,12 @@
   ensureActiveTextEditor: (fn)->
     editor = atom.workspace.getActiveTextEditor()
     if !editor
-      @triggerPush = false
+      @engageTabListener = false
       atom.workspace.open().then (editor)->
-        @triggerPush = true
+        @engageTabListener = true
         fn(editor)
     else
-      @triggerPush = true
+      @engageTabListener = true
       fn(editor)

   pairingSetup: ->
@@ -210,7 +210,6 @@
       editor = e.item
       return unless editor.constructor.name is "TextEditor"
       sharePane = @createSharePane(editor)
-      return unless @triggerPush
       @queue.add(@globalChannel.name, 'client-create-share-pane', {
         to: 'all',
         from: @markerColour,


Remove labels from find in buffer

Remove labels from find in buffer

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -12,10 +12,8 @@
       @ul outlet: 'infoMessages', class: 'info-messages block'

       @div class: 'find-container block', =>
-        @label class: 'text-subtle', 'Find'
-
         @div class: 'editor-container', =>
-          @subview 'findEditor', new Editor(mini: true)
+          @subview 'findEditor', new Editor(mini: true, placeholderText: 'Find in current buffer')

           @div class: 'find-meta-container', =>
             @span outlet: 'resultCounter', class: 'text-subtle result-counter', ''
@@ -28,10 +26,8 @@
           @button outlet: 'selectionOptionButton', class: 'btn btn-mini option-selection', '"'

       @div class: 'replace-container block', =>
-        @label class: 'text-subtle', 'Replace'
-
         @div class: 'editor-container', =>
-          @subview 'replaceEditor', new Editor(mini: true)
+          @subview 'replaceEditor', new Editor(mini: true, placeholderText: 'Replace in current buffer')

         @div class: 'btn-group btn-toggle', =>
           @button outlet: 'replaceNextButton', class: 'btn btn-mini btn-next', 'Next'


Adding Less + Handlebars/Mustache languages

Unfortunately, there are no lexers available for these languages
but we can approximate them w/ Sass (for Less) and HTML (for HBS/MS)

When/If proper lexers become available, they should be used.

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -41,6 +41,12 @@
     singleLineComment: ['//']
     ignorePrefix:      '}'

+  Handlebars:
+    nameMatchers:      ['.handlebars', '.hbs']
+    pygmentsLexer:     'html' # TODO: is there a handlebars/mustache lexer? Nope. Lame.
+    multiLineComment:  ['{{!', '', '}}']
+    ignorePrefix:      '#'
+
   Haskell:
     nameMatchers:      ['.hs']
     pygmentsLexer:     'haskell'
@@ -78,6 +84,12 @@
     singleLineComment: ['%']
     ignorePrefix:      '}'

+  LESS:
+    nameMatchers:      ['.less']
+    pygmentsLexer:     'sass' # TODO: is there a less lexer? No. Maybe in the future.
+    singleLineComment: ['//']
+    ignorePrefix:      '}'
+
   Lua:
     nameMatchers:      ['.lua']
     pygmentsLexer:     'lua'
@@ -89,6 +101,12 @@
     pygmentsLexer:     'make'
     singleLineComment: ['#']
     ignorePrefix:      '}'
+
+  Mustache:
+    nameMatchers:      ['.mustache']
+    pygmentsLexer:     'html' # TODO: is there a handlebars/mustache lexer? Nope. Lame.
+    multiLineComment:  ['{{!', '', '}}']
+    ignorePrefix:      '#'

   'Objective-C':
     nameMatchers:      ['.m', '.mm']
@@ -154,4 +172,4 @@
     nameMatchers:      ['.yml', '.yaml']
     pygmentsLexer:     'yaml'
     singleLineComment: ['#']
-    ignorePrefix:      '}'
+    ignorePrefix:      '}'


added pattern for dataset name

added pattern for dataset name

--- app/assets/javascripts/admin/views/dataset/dataset_upload_view.coffee
+++ app/assets/javascripts/admin/views/dataset/dataset_upload_view.coffee
@@ -16,7 +16,7 @@

             Name

-            <input type="text" required name="name" value="" class="form-control" autofocus>
+            <input type="text" required name="name" value="" class="form-control" autofocus pattern="^[0-9a-zA-Z_\-]+$">


fix actionHandler mess

fix actionHandler mess

--- src/client/widget.coffee
+++ src/client/widget.coffee
@@ -20,6 +20,23 @@
     app: 'app'
     org: 'org'

+  actionHandler = (e)->
+    try
+      source  = $(e.currentTarget)
+      action  = source.data("hull-action")
+      fn = @actions[action] || @["#{action}Action"]
+      unless _.isFunction(fn)
+        throw new Error("Can't find action #{action} on this Widget")
+      options = {}
+      options[decamelize(k).replace("hull_", "")] = v for k,v of source.data()
+      fn.call(@, source, e, options)
+    catch e
+      console.error("oops... missed action?", e.message, e)
+    finally
+      e.stopPropagation()
+      e.stopImmediatePropagation()
+
+
   class HullWidget extends Backbone.View

     actions: {}
@@ -35,7 +52,7 @@
       try
         @events = if _.isFunction(@events) then @events() else @events
         @events ?= {}
-        @events["click [data-hull-action]"] = 'actionHandler'
+        @events["click [data-hull-action]"] = actionHandler

         # Building actions hash
         @actions = if _.isFunction(@actions) then @actions() else @actions
@@ -46,7 +63,7 @@
         unless @className?
           @className = "hull-widget"
           @className += " hull-#{@namespace}" if @namespace?
-        _.each _.functions(@actions), (f)=> @actions[f] = _.bind(@actions[f], @)
+
         @datasources = _.extend({}, default_datasources, @datasources || {}, options.datasources || {})
         @sandbox.on(refreshOn, (=> @refresh()), @) for refreshOn in (@refreshEvents || [])
       catch e
@@ -139,22 +156,6 @@
       @$el.html(ret)
       return @

-    actionHandler: (e)=>
-      try
-        source  = $(e.currentTarget)
-        action  = source.data("hull-action")
-        fn = @actions[action] || @["#{action}Action"]
-        unless _.isFunction(fn)
-          throw new Error("Can't find action #{action} on this Widget")
-        options = {}
-        options[decamelize(k).replace("hull_", "")] = v for k,v of source.data()
-        fn.call(@, source, e, options)
-      catch e
-        console.error("oops... missed action?", e.message, e)
-      finally
-        e.stopPropagation()
-        e.stopImmediatePropagation()
-
     afterRender: (data)=> data


Scroll to top on page forward only on old android, handle back case

* Back is always true when going back so set to false after back has
been performed so its correc the next time wise round.

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -101,9 +101,6 @@
         @displayPage targetPage, currentPage
     , 10

-    if @isAndroid() and @os.version < '4'
-      window.scrollTo 0,0
-
   # Private: Perform a slide transition.
   #
   # Returns nothing.
@@ -202,6 +199,11 @@
   displayPage: (targetPage, currentPage) ->
     targetPage.style.display = "-webkit-box"
     currentPage.style.display = "none"
+
+    if @isAndroid() and @os.version < '4' and @back is false
+      window.scrollTo 0,0
+
+    if @back is true then @back = false

   # Private: Hide DOM that has just been transitioned
   #


fix: material value overrides by material type becaue of substring match

--- src/coffee/header.coffee
+++ src/coffee/header.coffee
@@ -71,7 +71,7 @@
         parts = head.split GLOBALS.DELIM_HEADER_LANGUAGE
         if _.size(parts) >= 2
           nameRegexp = new RegExp("^#{langAttribName}\.")
-          if head.match(nameRegexp)
+          if head.match(nameRegexp) && _.first(parts) == langAttribName # because materialType override material attribute because of sub string match
             lang = _.last(parts)
             # TODO: check language
             langH2i[langAttribName] or= {}


KDFramework: some local storage related methods removed

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -868,39 +868,39 @@

     @getSingleton("windowController").setKeyView @

-  setPreserveValue:(preserveValue={})->
-    storedValue = @getSingleton('localStorageController').getValueById preserveValue.name
-
-    if "string" is typeof preserveValue.saveEvents
-      preserveValue.saveEvents = preserveValue.saveEvents.split(' ')
-    if "string" is typeof preserveValue.clearEvents
-      preserveValue.clearEvents = preserveValue.clearEvents.split(' ')
-
-    for preserveEvent in preserveValue.saveEvents
-      @on preserveEvent, (event)=>
-        value = @getOptions().preserveValue.getValue?() ? @getValue?()
-        @savePreserveValue preserveValue.name, value
-
-    for preserveEvent in preserveValue.clearEvents
-      @on preserveEvent, (event)=>
-        @clearPreserveValue()
-
-    if preserveValue.displayEvents then for displayEvent in preserveValue.displayEvents
-      @on displayEvent, (event)=>
-        @applyPreserveValue storedvalue if storedValue
-
-    if storedValue
-      @utils.defer => @applyPreserveValue storedValue
-
-  applyPreserveValue:(value)->
-    if @getOptions().preserveValue.setValue
-      @getOptions().preserveValue.setValue value
-    else @setValue? value
-
-  savePreserveValue:(id,value)->
-    @getSingleton('localStorageController').setValueById id, value
-
-  clearPreserveValue:->
-    if @getOptions().preserveValue
-      @getSingleton('localStorageController').deleteId @getOptions().preserveValue.name
-
+  # setPreserveValue:(preserveValue={})->
+  #   storedValue = @getSingleton('localStorageController').getValueById preserveValue.name
+
+  #   if "string" is typeof preserveValue.saveEvents
+  #     preserveValue.saveEvents = preserveValue.saveEvents.split(' ')
+  #   if "string" is typeof preserveValue.clearEvents
+  #     preserveValue.clearEvents = preserveValue.clearEvents.split(' ')
+
+  #   for preserveEvent in preserveValue.saveEvents
+  #     @on preserveEvent, (event)=>
+  #       value = @getOptions().preserveValue.getValue?() ? @getValue?()
+  #       @savePreserveValue preserveValue.name, value
+
+  #   for preserveEvent in preserveValue.clearEvents
+  #     @on preserveEvent, (event)=>
+  #       @clearPreserveValue()
+
+  #   if preserveValue.displayEvents then for displayEvent in preserveValue.displayEvents
+  #     @on displayEvent, (event)=>
+  #       @applyPreserveValue storedvalue if storedValue
+
+  #   if storedValue
+  #     @utils.defer => @applyPreserveValue storedValue
+
+  # applyPreserveValue:(value)->
+  #   if @getOptions().preserveValue.setValue
+  #     @getOptions().preserveValue.setValue value
+  #   else @setValue? value
+
+  # savePreserveValue:(id,value)->
+  #   @getSingleton('localStorageController').setValueById id, value
+
+  # clearPreserveValue:->
+  #   if @getOptions().preserveValue
+  #     @getSingleton('localStorageController').deleteId @getOptions().preserveValue.name
+


prevent shift select between folders

prevent shift select between folders


Use new hooks for updating editors

Use new hooks for updating editors


router: improve route handling with entry point

assign second frag as name if route's first frag is same as entry point
slug

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -44,14 +44,11 @@
       console.warn "Contract warning: shared route #{route} is not implemented."

   handleRoute:(route, options={})->
-
-    {entryPoint} = options
+    entryPoint = options.entryPoint or KD.config.entryPoint
+    frags      = route.split("?")[0].split "/"
+    name       = if frags[1] is entryPoint?.slug then frags[2] else frags[1]

     appManager = KD.getSingleton 'appManager'
-    frags      = route.split '/'
-    name       = (if entryPoint?.type is "group" then frags[2] else frags[1]) or ''
-    name       = (name.split '?')[0]
-
     if appManager.isAppInternal name
       return KodingAppsController.loadInternalApp name, (err)=>
         return warn err  if err


AppController: Removed reaload browser copies.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1725,7 +1725,7 @@
   showKickedModal: ->
     options        =
       title        : 'Your session has been closed'
-      content      : "You have been removed from the session by @#{@collaborationHost}. - Please reload your browser -"
+      content      : "You have been removed from the session by @#{@collaborationHost}."
       blocking     : yes
       buttons      :
         ok         :
@@ -1749,7 +1749,7 @@

   showSessionEndedModal: (content) ->

-    content ?= "This session is ended by @#{@collaborationHost} You won't be able to access it anymore. - Please reload your browser -"
+    content ?= "This session is ended by @#{@collaborationHost} You won't be able to access it anymore."

     options        =
       title        : 'Session ended'


don't return the tx_unlisten function, its not used anywhere and its quite an odd return value for that function

--- client/tx/lib/tx-builder.coffee
+++ client/tx/lib/tx-builder.coffee
@@ -88,9 +88,6 @@
     # TODO validate signature
     try show_dialog tx, 'other'
     catch e then display_error e
-
-  # Calling the returned function will stop listening on the channel
-  tx_unlisten

 # Build transaction with the given inputs and parse outputs from <form>
 build_tx = (inputs, $form) ->


Ace: preview only for the files under ~/Web

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -124,10 +124,14 @@
     {path, vmName} = @getActiveAceView().getData()
     return  if /^localfile/.test path
     path = KD.getPublicURLOfPath path
-    KD.singleton("appManager").create "Viewer", {path, vmName}, (app) =>
-      @tabView.addPane new KDTabPaneView
-        name    : "[#{path.split("/").last}]"
-        view    : app.getView()
+
+    if path
+      KD.singleton("appManager").require "Viewer", {path, vmName}, (app) =>
+        @tabView.addPane new KDTabPaneView
+          name    : "[#{path.split("/").last}]"
+          view    : app.getView()
+    else
+      @getActiveAceView().ace.notify "File needs to be under ~/Web folder", "error"

   reopenLastSession: ->
     data   = @sessionData


Fix client exception when accessing window location, refs #519

--- client/app/views/main.coffee
+++ client/app/views/main.coffee
@@ -260,11 +260,19 @@
         # only if there is a hash in the home given url.
         else if hash
             contentWindow = frame.prop('contentWindow')
-            currentHash = contentWindow.location.hash.substring 1
+            # Same origin policy may prevent to access location hash
+            try
+                currentHash = contentWindow.location.hash.substring 1
+            catch err
+                console.err err
             onLoad()

         else if frame.is(':visible')
-            frame.prop('contentWindow').location.hash = ''
+            # Same origin policy may prevent to access location hash
+            try
+                frame.prop('contentWindow').location.hash = ''
+            catch err
+                console.err err
             onLoad()

         else


increase factorMaxSlip, when lap is finished and the maximum angle experienced is very low

--- coffeescript/lib/bot/bot.coffee
+++ coffeescript/lib/bot/bot.coffee
@@ -13,6 +13,7 @@
       @throttles = []
       @maxSlipInitialized = no
       @factorMaxSlip = 0.5
+      @maxSlipAngle = 0

 #      if @color is 'red'
 #        @throttle = 0.7
@@ -35,7 +36,7 @@
       @initMaxSlipFactor()

       @adjustVelocity()
-
+      @maxSlipAngle = Math.max(@maxSlipAngle, @race.getCarAngle(@color))

       if @race.currentTick < 4
         @throttle = 1.0
@@ -51,6 +52,13 @@
         winston.info "#{@logPrefix()};Crash, Curved vel old #{@factorMaxSlip}"
         @reduceFactorMaxSlip()
         winston.info "#{@logPrefix()};Crash, Curved vel new #{@factorMaxSlip}"
+      control.ignore()
+
+    lapFinished: (data, control) ->
+      angleDiff = @race.maxAngle - @maxSlipAngle
+      if angleDiff > 0
+        @factorMaxSlip = @factorMaxSlip * (1 + angleDiff * angleDiff / 10000)
+      @maxSlipAngle = 0
       control.ignore()

     reduceFactorMaxSlip: ->
@@ -140,7 +148,7 @@
         @targetVelocity = 100
         return @adjustThrottle()

-      if @onBendedPiece() and @predictAngle() > @race.maxAngle - 1
+      if @onBendedPiece() and @predictAngle() > @race.maxAngle
         @targetVelocity = @maxVelocityForRadius @getRadius() / 2
         return @adjustThrottle()

@@ -157,7 +165,7 @@
       angleS = @race.getAngularSpeed(@color)
       angleSC = @race.getAngularSpeedChange(@color)
       velocity = @race.getVelocity @color
-      Math.abs(angle + (velocity + 2) * angleS + velocity * angleSC)
+      Math.abs(angle + (velocity + 1) * angleS + velocity * angleSC)

     maxVelocityForRadius: (radius) ->
       # v^2 / r = @factorMaxSlip


Add test helper to render into document

Add test helper to render into document

--- components/__tests__/utils.cjsx
+++ components/__tests__/utils.cjsx
@@ -1,8 +1,11 @@
-React     = require('react/addons')
 TestUtils = React.addons.TestUtils

 module.exports =
-  # Generates a shallow render for a given component with properties and children
+  renderComponent: (Component, props={}, state={}) ->
+    component = TestUtils.renderIntoDocument(<Component {...props}/>)
+    component.setState(state) unless state == {}
+    component
+
   shallowRenderComponent: (component, props, children...) ->
     shallowRenderer = TestUtils.createRenderer()
     shallowRenderer.render(React.createElement(component, props,


Unregisterwindow resize when its not necessary

--- client/app/Applications/Chat.kdapplication/Views/mainchatpanel.coffee
+++ client/app/Applications/Chat.kdapplication/Views/mainchatpanel.coffee
@@ -16,8 +16,6 @@
       if visible
         @getSingleton('windowController').addLayer @
         @once 'ReceivedClickElsewhere', @bound 'hide'
-
-    @listenWindowResize()

   createConversation:(data)->
     # Data includes chatChannel and the conversation
@@ -55,7 +53,8 @@
       @setWidth 250
       @inboxMode = no

+    @listenWindowResize @inboxMode
+
   _windowDidResize:->
-    if @inboxMode
-      {winWidth} = @getSingleton('windowController')
-      @setWidth winWidth - 160
+    {winWidth} = @getSingleton('windowController')
+    @setWidth winWidth - 160


Add Public/Protected/Private Functions

Add Public/Protected/Private Functions

--- snippets/php.cson
+++ snippets/php.cson
@@ -37,3 +37,27 @@
                 $2
             }
         """
+
+    'PHP Public Function Based on PhpStorm':
+      'prefix': 'pubf'
+      'body': """
+        public function $1($2) {
+            $0
+        }
+      """
+
+    'PHP Protected Function Based on PhpStorm':
+      'prefix': 'prof'
+      'body': """
+        protected function $1($2) {
+            $0
+        }
+      """
+
+    'PHP Private Function Based on PhpStorm':
+      'prefix': 'prif'
+      'body': """
+        private function $1($2) {
+            $0
+        }
+      """


Use a span to hold the cursor position text

This fixes an issue where detaching and re-attaching the status bar
would cause the cursor position div not to be shown. The reason
why it wasn't shown is that it would get an inline display: block style
for some reason (overriding the display: inline-block), which breaks layout.
I'm guessing that this is a jQuery thing related to how it handles
hiding/showing DOM nodes.

--- lib/cursor-position-view.coffee
+++ lib/cursor-position-view.coffee
@@ -3,7 +3,8 @@
 module.exports =
 class CursorPositionView extends View
   @content: ->
-    @div class: 'cursor-position inline-block'
+    @div class: 'cursor-position inline-block', =>
+      @span outlet: 'cursorPosition'

   initialize: (@statusBar) ->
     @subscribe @statusBar, 'active-buffer-changed', @updateCursorPositionText
@@ -18,6 +19,6 @@
   updateCursorPositionText: =>
     editor = atom.workspace.getActiveEditor()
     if position = editor?.getCursorBufferPosition()
-      @text("#{position.row + 1},#{position.column + 1}").show()
+      @cursorPosition.text("#{position.row + 1},#{position.column + 1}").show()
     else
-      @hide()
+      @cursorPosition.hide()


Don't associate a request id if we don't have a real one from facebook

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -200,7 +200,7 @@
         fb_data = $.extend({}, opts, carrotResponse.fb_data)
         postMethod(fb_data,
           (fbResponse) =>
-            if fbResponse
+            if fbResponse && fbResponse.request
               @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{carrotResponse.request_id}/ids", {platform_id: fbResponse.request})
               if fbResponse && fbResponse.to
                 for receivingUser in fbResponse.to


Coherent name

Coherent name

--- library/exposure/publish.coffee
+++ library/exposure/publish.coffee
@@ -43,7 +43,7 @@
 # encoded with JSON. Please refer to the implementation for info.
 # In addition to the documentation it also exposes a complement
 # set of service related data, such as health status and so on.
-module.exports.Publisher = class Publisher extends Barebones
+module.exports.Publish = class Publish extends Barebones

     # These invocations establish the parameters which are going
     # to be used for matching HTTP requests against this service.


fix up some of the cakefile coding

fix up some of the cakefile coding

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -120,8 +120,8 @@
             assert sources = try readdirSyncRecursive artifacts
             assert sources = _.reject sources, isDirS artifacts
             o = (source) -> optimize("#{artifacts}/#{source}")
-            logger.info produce, artifacts, s for s in sources
-            o s; logger.info op, artifacts, s for s in sources
+            try logger.info produce, artifacts, s for s in sources
+            (o(s); logger.info(op, artifacts, s)) for s in sources
             logger.info success.green if status is 0

     # This task launches an instance of application where this task


update the last worked on when a worker gets an entity

--- src/Worker.coffee
+++ src/Worker.coffee
@@ -97,6 +97,9 @@
             # Add the workerUuid
             data.workerUuid = nextWorkerHeartbeat.uuid

+            # update the last worked on
+            data.last_worked_on = (new Date).getTime()
+
             # Send data to the next worker
             nextWorkerEndpoint.create data, (error, result) ->
               unless error


Properly exports vm.deltaCopy().

Properly exports vm.deltaCopy().

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -538,6 +538,18 @@

 deltaCopy = ({ vm, sr }) -> @deltaCopyVm(vm, sr)

+deltaCopy.params = {
+  vm: { type: 'string' },
+  sr: { type: 'string' }
+}
+
+deltaCopy.resolve = {
+  vm: [ 'vm', 'VM', 'operate'],
+  sr: [ 'sr', 'SR', 'operate']
+}
+
+exports.deltaCopy = deltaCopy
+
 #---------------------------------------------------------------------

 rollingSnapshot = $coroutine ({vm, tag, depth}) ->


Sets a long timeout for vm.import().

Sets a long timeout for vm.import().

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -738,6 +738,10 @@
 #---------------------------------------------------------------------

 handleVmImport = $coroutine (req, res, { xapi }) ->
+  # Timeout seems to be broken in Node 4.
+  # See https://github.com/nodejs/node/issues/3319
+  req.setTimeout(43200000) # 12 hours
+
   contentLength = req.headers['content-length']
   if !contentLength
     res.writeHead(411)


vm.clone() is only authorized for admins.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -621,6 +621,9 @@

   return true

+# Having permission on a VM is not enough to be able to clone it.
+clone.permission = 'admin'
+
 clone.params = {
   id: { type: 'string' }
   name: { type: 'string' }


Bugfix to code for precompilation directive.

--- src/bundle.coffee
+++ src/bundle.coffee
@@ -70,9 +70,10 @@
             # strip out querystring, if any, and turn
             # into an absolute path
             src = normalize src
-            path = absolutize src, root
+            # path = absolutize src, root
             type = ($ @).attr 'type'
-            {src, type}
+            precompile = ($ @).data 'precompile'
+            {src, type, precompile}
         .get()
         .filter (script) ->
             script.src isnt utils.jQueryPath
@@ -86,7 +87,7 @@
         .map ->
             src = ($ @).attr 'href'
             src = normalize src
-            path = absolutize src, root
+            # path = absolutize src, root
             type = ($ @).attr 'type'
             {src, type}
         .get()
@@ -148,8 +149,8 @@
             $ = window.$
             scripts = getScripts window, @root
             scripts.forEach (script) =>
-                if ($ script).data 'precompile'
-                    s = @find '/' + normalize script.src
+                if script.precompile
+                    s = @find '/' + script.src
                     s.compilerType = 'precompiler'

             callback()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -1,19 +1,24 @@
+oldCamera = Camera
 Camera = (I={}) ->
   Object.reverseMerge I,
-    cameraBounds: Rectangle
+    cameraBounds: Rectangle # World Coordinates
       x: 0
       y: 0
       width: App.width
       height: App.height
-    screen: Rectangle
+    screen: Rectangle # Screen Coordinates
       x: 0
       y: 0
       width: App.width
       height: App.height
-    deadzone: Point(0, 0)
+    deadzone: Point(0, 0) # Screen Coordinates
     zoom: 1
     transform: Matrix()
-    scroll: Point(0, 0)
+    x: App.width/2 # World Coordinates
+    y: App.height/2 # World Coordinates
+    velocity: Point.ZERO
+    maxSpeed: 25
+    t90: 2 # Time in seconds for camera to move 90% of the way to the target

   currentType = "centered"
   currentObject = null
@@ -22,25 +27,30 @@
   transformFilters = []

   focusOn = (object) ->
-    objectCenter = object.center()
+    dt = 1 / 30 # TODO: Use engine FPS
+    dampingFactor = 2

-    centerOffset = objectCenter.subtract(I.screen.width / 2, I.screen.height / 2)
+    #TODO: Different t90 value inside deadzone?

-    deadzone = I.deadzone.scale(1 / (2 * I.zoom))
+    c = dt * 3.75 / I.t90
+    if c >= 1
+      # Spring is configured to be too intense, just snap to target
+      self.position(target)
+      I.velocity = Point.ZERO
+    else
+      objectCenter = object.center()
+      objectVelocity = object.I.velocity
+
+      if objectVelocity
+        target = objectCenter.add(objectVelocity.scale(5))
+      else
+        target = objectCenter

-    centerRect = Rectangle
-      x: centerOffset.x - deadzone.x
-      y: centerOffset.y - deadzone.y
-      width: 2 * deadzone.x
-      height: 2 * deadzone.y
+      delta = target.subtract(self.position())

-    I.scroll = Point(
-      I.scroll.x.clamp(centerRect.left, centerRect.right)
-      I.scroll.y.clamp(centerRect.top, centerRect.bottom)
-    )
-
-    I.scroll.x = I.scroll.x.clamp(I.cameraBounds.left, I.cameraBounds.right - I.screen.width)
-    I.scroll.y = I.scroll.y.clamp(I.cameraBounds.top, I.cameraBounds.bottom - I.screen.height)
+      force = delta.subtract(I.velocity.scale(dampingFactor))
+      self.changePosition(I.velocity.scale(c).clamp(I.maxSpeed))
+      I.velocity = I.velocity.add(force.scale(c))

   followTypes =
     centered: (object) ->
@@ -68,35 +78,38 @@
       currentObject = object
       currentType = type

-      # TODO: Easing
-      I.scroll = object.center()
-
     objectFilterChain: (fn) ->
       objectFilters.push fn

     transformFilterChain: (fn) ->
       transformFilters.push fn

-  self.attrAccessor "transform", "scroll"
+  self.include(Bindable)

-  self.include(Bindable)
+  self.attrAccessor "transform"

   self.bind "afterUpdate", ->
     if currentObject
       followTypes[currentType](currentObject)

-    I.transform = Matrix.translate(-I.scroll.x, -I.scroll.y)
+    # Hard clamp camera to world bounds
+    I.x = I.x.clamp(I.cameraBounds.left + I.screen.width/2, I.cameraBounds.right - I.screen.width/2)
+    I.y = I.y.clamp(I.cameraBounds.top + I.screen.height/2, I.cameraBounds.bottom - I.screen.height/2)
+
+    I.transform = Matrix.translate(-I.x, -I.y)

   self.bind "draw", (canvas, objects) ->
+    # Move to correct screen coordinates
     canvas.withTransform Matrix.translate(I.screen.x, I.screen.y), (canvas) ->
       canvas.clip(0, 0, I.screen.width, I.screen.height)

       objects = objectFilters.pipeline(objects)
       transform = transformFilters.pipeline(self.transform().copy())

-      canvas.withTransform transform, (canvas) ->
-        self.trigger "beforeDraw", canvas
-        objects.invoke "draw", canvas
+      canvas.withTransform Matrix.translation(I.screen.width/2, I.screen.height/2), (canvas) ->
+        canvas.withTransform transform, (canvas) ->
+          self.trigger "beforeDraw", canvas
+          objects.invoke "draw", canvas

       self.trigger 'flash', canvas

@@ -107,6 +120,10 @@

       objects.invoke "trigger", "overlay", canvas

+  self.include(Bounded)
+
+  # The order of theses includes is important for
+  # the way in wich they modify the camera view transform
   self.include(Camera.ZSort)
   self.include(Camera.Zoom)
   self.include(Camera.Rotate)
@@ -116,3 +133,5 @@

   return self

+
+Object.extend(Camera, oldCamera)


Update chosen.coffee

Update chosen.coffee

Adding conditional for case of no ngModel

--- src/chosen.coffee
+++ src/chosen.coffee
@@ -53,7 +53,8 @@
       $timeout -> element.chosen options

       #Watch the underlying ng-model for updates and trigger an update when they occur.
-      ctrl.$render = -> $timeout -> element.trigger('chosen:updated')
+      if ctrl
+        ctrl.$render = -> $timeout -> element.trigger('chosen:updated')

       # Watch the collection in ngOptions and update chosen when it changes.  This works with promises!
       if attr.ngOptions


Fix uppercased brew.

Fix uppercased brew.

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -222,7 +222,7 @@
     robot.brain.save()

     if hasBounty()
-      msg.send "@team A bountied Brew has been started by @#{brewing.barista}! #{statusEmoji.random('success')}\n" +
+      msg.send "@team A bountied brew has been started by @#{brewing.barista}! #{statusEmoji.random('success')}\n" +
           "@#{brewing.barista} will be rewarded #{bounty.reward} :coffee:s for this brew.\n" +
           "To grab a spot use: `#{robot.alias}dibs`\n" +
           "To end the brew use: `#{robot.alias}fresh pot`"


color2

--- src/color2.coffee
+++ src/color2.coffee
@@ -6,7 +6,7 @@
     return undefined unless channels = rgbParser.exec(colorString)

     parsedColor = (parseFloat channel for channel in channels[1..3])
-    parsedColor[3] ||= 1.0
+    parsedColor[3] ||= 1

     return parsedColor

@@ -16,9 +16,9 @@
     switch hexString.length
       when 3, 4
         if hexString.length == 4
-          alpha = ((parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0)
+          alpha = ((parseInt(hexString.substr(3, 1), 16) * 0x11) / 255)
         else
-          alpha = 1.0
+          alpha = 1

         rgb = (parseInt(hexString.substr(i, 1), 16) * 0x11 for i in [0..2])
         rgb.push(alpha)
@@ -27,9 +27,9 @@

       when 6, 8
         if hexString.length == 8
-          alpha = (parseInt(hexString.substr(6, 2), 16) / 255.0)
+          alpha = (parseInt(hexString.substr(6, 2), 16) / 255)
         else
-          alpha = 1.0
+          alpha = 1

         rgb = (parseInt(hexString.substr(2 * i, 2), 16) for i in [0..2])
         rgb.push(alpha)
@@ -44,15 +44,15 @@

     parsedColor = (parseFloat channel for channel in channels[1..3])
     parsedColor[0] = parsedColor[0]
-    parsedColor[3] ||= 1.0
+    parsedColor[3] ||= 1

     return hslToRgb(parsedColor)

   hslToRgb = (hsl) ->
     [h, s, l, a] = (parseFloat(channel) for channel in hsl)

-    h /= 360.0
-    a ||= 1.0
+    h /= 360
+    a ||= 1

     r = g = b = null

@@ -74,7 +74,7 @@
       g = hueToRgb(p, q, h)
       b = hueToRgb(p, q, h - 1/3)

-      rgbMap = (channel * 0xFF for channel in [r, g, b])
+      rgbMap = ((channel * 0xFF).round() for channel in [r, g, b])

     return rgbMap.concat(a)

@@ -124,7 +124,6 @@
       @copy().complement$()

     complement$: ->
-      hsl = @toHsl()
       @hue$(180)

     copy: ->
@@ -176,7 +175,7 @@
     hue$: (degrees) ->
       hsl = @toHsl()

-      hsl[0] = (hsl[0] + degrees) % 360
+      hsl[0] = (hsl[0] + degrees).mod 360

       [@r, @g, @b, @a] = hslToRgb(hsl)

@@ -199,10 +198,11 @@
     mixWith$: (other, amount) ->
       amount ||= 0.5

-      mixedColors = [@r, @g, @b, @a].zip([other.r, other.g, other.b, other.a]).map (array) ->
+      [@r, @g, @b, @a] = [@r, @g, @b, @a].zip([other.r, other.g, other.b, other.a]).map (array) ->
         (array[0] * amount) + (array[1] * (1 - amount))

-      [@r, @g, @b, @a] = mixedColors
+      [@r, @g, @b] = [@r, @g, @b].map (color) ->
+        color.round()

       return this

@@ -229,25 +229,31 @@
       "##{hexFromNumber(@r)}#{hexFromNumber(@g)}#{hexFromNumber(@b)}"

     toHsl: ->
-      [r, g, b] = (channel / 255.0 for channel in [@r, @g, @b])
-
-      min = Math.min(r, g, b)
-      max = Math.max(r, g, b)
-
-      hue = saturation = lightness = (max + min) / 2.0
+      [r, g, b] = (channel / 255 for channel in [@r, @g, @b])
+
+      {min, max} = [r, g, b].extremes()
+
+      hue = saturation = lightness = (max + min) / 2

       if max == min
         hue = saturation = 0
       else
         chroma = max - min
-        saturation = (if lightness > 0.5 then chroma / (2.0 - 2.0 * lightness) else chroma / (lightness * 2.0))
+
+        saturation =
+          if lightness > 0.5
+            chroma / (1 - lightness)
+          else
+            chroma / lightness
+
+        saturation /= 2

         switch max
-          when r then hue = ((g - b) / chroma) + (if g < b then 6 else 0)
-          when g then hue = ((b - r) / chroma) + 2.0
-          when b then hue = ((r - g) / chroma) + 4.0
-
-        hue *= 60.0
+          when r then hue = ((g - b) / chroma) + 0
+          when g then hue = ((b - r) / chroma) + 2
+          when b then hue = ((r - g) / chroma) + 4
+
+        hue *= 60

       return [hue, saturation, lightness, @a]


Remove unnecessary call to set jquery plugin name

--- src/common.coffee
+++ src/common.coffee
@@ -320,8 +320,5 @@
       for option of options
         @settings[option] = options[option]

-    # set name
-    @_name = pluginName
-
     # call draw function
     @init()


Save config when deprerecated Config::set signature is used

--- src/config.coffee
+++ src/config.coffee
@@ -583,6 +583,7 @@
         Pass a `scopeSelector` in an options hash as the final argument instead.
       """
       [scopeSelector, keyPath, value] = arguments
+      shouldSave = true
     else
       [keyPath, value, options] = arguments
       scopeSelector = options?.scopeSelector


Fixed Config->deletePlugin() to pass tests

--- src/config.coffee
+++ src/config.coffee
@@ -125,8 +125,7 @@
         if index is false
           false
         else
-          delete self.config[hostname][index]
-
+          @config[hostname].splice index, 1
           @save()
           @config[hostname]


Client: Activity: Updated scrollerMixin. Deleted loadedWithScroll prop and using isThresholdReached and isPageLoaded parameters

--- client/app/lib/components/scroller/scrollermixin.coffee
+++ client/app/lib/components/scroller/scrollermixin.coffee
@@ -16,19 +16,20 @@
     # we can not catch 0px to scroll to bottom. If scroll near about 100px or less
     # and when new message received we make scroll to bottom so user can see new messages.
     # If not probably user is reading old messages and we don't make scroll to bottom.
-    return @scrollHeight - (@scrollTop + offsetHeight) < 100
+    return @scrollHeight - (@scrollTop + offsetHeight) < 10

-  componentDidUpdate: ->
+  componentDidUpdate: (prevProps) ->

     return  unless @refs?.scrollContainer

     element = React.findDOMNode @refs.scrollContainer

-    if @loadedWithScroll
+    if @shouldScrollToBottom or @isPageLoaded
+      element.scrollTop = element.scrollHeight
+    else if @isThresholdReached
       element.scrollTop = @scrollTop + (element.scrollHeight - @scrollHeight)
-    else if @shouldScrollToBottom
-      element.scrollTop = element.scrollHeight
+      @isThresholdReached = no

   setScrollPosition: ->
@@ -43,6 +44,7 @@

   componentDidMount: ->

+    @isPageLoaded = yes
     window.addEventListener "resize", @bound 'setScrollPosition'


Clean up the syntax scopes section

Clean up the syntax scopes section

--- src/editor.coffee
+++ src/editor.coffee
@@ -1389,30 +1389,8 @@
     @displayBuffer.reloadGrammar()

   ###
-  Section: Syntatic Queries
-  ###
-
-  # Essential: Get the syntactic scopes for the given position in buffer
-  # coordinates.
-  #
-  # For example, if called with a position inside the parameter list of an
-  # anonymous CoffeeScript function, the method returns the following array:
-  # `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]`
-  #
-  # * `bufferPosition` A {Point} or {Array} of [row, column].
-  #
-  # Returns an {Array} of {String}s.
-  scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
-
-  # Essential: Get the range in buffer coordinates of all tokens surrounding the
-  # cursor that match the given scope selector.
-  #
-  # For example, if you wanted to find the string surrounding the cursor, you
-  # could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`.
-  #
-  # Returns a {Range}.
-  bufferRangeForScopeAtCursor: (selector) ->
-    @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())
+  Section: Managing Syntax Scopes
+  ###

   # Public: Get the syntactic scopes for the most recently added cursor's
   # position. See {::scopesForBufferPosition} for more information.
@@ -1422,6 +1400,28 @@
   getCursorScopes: ->
     deprecate 'Use Editor::scopesAtCursor() instead'
     @scopesAtCursor()
+
+  # Essential: Get the syntactic scopes for the given position in buffer
+  # coordinates.
+  #
+  # For example, if called with a position inside the parameter list of an
+  # anonymous CoffeeScript function, the method returns the following array:
+  # `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]`
+  #
+  # * `bufferPosition` A {Point} or {Array} of [row, column].
+  #
+  # Returns an {Array} of {String}s.
+  scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
+
+  # Extended: Get the range in buffer coordinates of all tokens surrounding the
+  # cursor that match the given scope selector.
+  #
+  # For example, if you wanted to find the string surrounding the cursor, you
+  # could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`.
+  #
+  # Returns a {Range}.
+  bufferRangeForScopeAtCursor: (selector) ->
+    @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())

   logCursorScope: ->
     console.log @getCursorScopes()


* engine default sandboxes

* engine default sandboxes

--- src/engine.coffee
+++ src/engine.coffee
@@ -7,9 +7,10 @@
 util                  = require 'util'
 vm                    = require 'vm'

+MAX_CACHED_SANDBOXES = 100
+
 sandboxCons = () ->
   vm.createContext({})
-

 class engine

@@ -17,7 +18,7 @@
     options             = options or {}
     @verbose            = options.verbose or false
     @minimize           = options.minimize or false
-    @pool               = new Pool(sandboxCons, options.poolSize or 100)
+    @pool               = new Pool(sandboxCons, options.poolSize or MAX_CACHED_SANDBOXES)
     @prettyPrintErrors      = if options.prettyPrintErrors? then options.prettyPrintErrors else true
     @prettyLogErrors        = if options.prettyLogErrors?   then options.prettyLogErrors   else true
     @autoEscape             = if options.autoEscape?        then options.autoEscape        else true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -9,6 +9,31 @@
     includedModules: []
     paused: false

+  ###*
+  The Engine controls the game world and manages game state. Once you
+  set it up and let it run it pretty much takes care of itself.
+
+  You can use the engine to add or remove objects from the game world.
+
+  There are several modules that can include to add additional capabilities
+  to the engine.
+
+  The engine fires events that you  may bind listeners to. Event listeners
+  may be bound with engine.bind(eventName, callback)
+
+  beforeAdd(entityData) Observer or modify the
+  entity data before it is added to the engine.
+
+  afterAdd(gameObject) Observe or
+  configure a gameObject that has been added to the engine.
+
+  draw(canvas) Called after the engine draws on the canvas, you
+  wish to draw additional things to the canvas.
+
+  update Called after the engine updates all the game objects.
+
+  @constructor
+  ###
   window.Engine = (I) ->
     I ||= {}

@@ -51,6 +76,17 @@
     canvas = I.canvas || $("<canvas />").powerCanvas()

     self = Core(I).extend
+      ###*
+      The add method creates and adds an object to the game world.
+
+      Events triggered:
+      beforeAdd(entityData)
+      afterAdd(gameObject)
+
+      @name add
+      @methodOf Engine#
+      @param entityData The data used to create the game object.
+      ###
       add: (entityData) ->
         self.trigger "beforeAdd", entityData

@@ -85,12 +121,22 @@
       eachObject: (iterator) ->
         I.objects.each iterator

+      ###*
+      Start the game simulation.
+      @methodOf Engine#
+      @name start
+      ###
       start: () ->
         unless intervalId
           intervalId = setInterval(() ->
             step()
           , 1000 / I.FPS)

+      ###*
+      Stop the simulation.
+      @methodOf Engine#
+      @name stop
+      ###
       stop: ->
         clearInterval(intervalId)
         intervalId = null
@@ -104,6 +150,11 @@
       play: ->
         I.paused = false

+      ###*
+      Pause the simulation
+      @methodOf Engine#
+      @name pause
+      ###
       pause: ->
         I.paused = true


Removed unused file

Removed unused file

--- core/app/assets/javascripts/base/bootstrap_alert.coffee
+++ core/app/assets/javascripts/base/bootstrap_alert.coffee
@@ -1,33 +0,0 @@
-$('body').on 'click', '[data-dismiss="alert"]', (e) ->
-  $this = $ @
-  selector = $this.attr 'data-target'
-
-  e.stopImmediatePropagation()
-  e.preventDefault()
-
-  if not selector
-    selector = $this.attr('href')
-    selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') #strip for ie7
-
-  $parent = $ selector
-
-  unless $parent.length
-    if $this.hasClass 'alert'
-      $parent = $this
-    else
-      $parent = $this.parent()
-
-  closeAlert($parent)
-
-closeAlert = ($alert) ->
-  $alert.trigger(e = $.Event('close'))
-
-  hideAlert = ->
-    $alert
-      .trigger('closed')
-      .fadeOut()
-
-  if $.support.transition and $alert.hasClass('fade')
-    $alert.on($.support.transition.end, hideAlert)
-  else
-    hideAlert()


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -3,7 +3,7 @@
 velocity so that it will approach another object.

 The calculated velocity is based on the center point of
-both objects.
+each objects.

 This method relies on both objects having `position` methods.
 All GameObjects have this method by default.


Improve Github pull request output by using Slack attachment formatting

--- src/github.coffee
+++ src/github.coffee
@@ -39,10 +39,13 @@

 module.exports = (robot) ->

-  send = (context, message, prependUsername=no) ->
-    robot.adapter.customMessage
-      channel: context.message.room
-      text: "#{if prependUsername then "<@#{context.message.user.id}> " else ""}#{message}"
+  send = (context, message) ->
+    payload = channel: context.message.room
+    if _(message).isString()
+      payload.text = message
+    else
+      payload = _(payload).extend message
+    robot.adapter.customMessage payload

   getNotifications = ->
     robot.brain.get('github-notifications') or []
@@ -124,6 +127,38 @@
     saveNotifications notificationsToKeep
     notifications.length - (notificationsToKeep.length)

+  buildGithubAttachment = (pr, assignee) ->
+    color: "#ff9933"
+    author_name: pr.user.login
+    author_icon: pr.user.avatarUrl
+    author_link: pr.user.htmlUrl
+    title: pr.title
+    title_link: pr.htmlUrl
+    fields: [
+      title: "Updated"
+      value: moment(pr.updatedAt).fromNow()
+      short: yes
+    ,
+      title: "Status"
+      value: if pr.mergeable then "Mergeable" else "Unresolved Conflicts"
+      short: yes
+    ,
+      title: "Assignee"
+      value: if assignee then "<@#{assignee.id}>" else "Unassigned"
+      short: yes
+    ,
+      title: "Lines"
+      value: "+#{pr.additions} -#{pr.deletions}"
+      short: yes
+    ]
+    fallback: """
+      *#{pr.title}* +#{pr.additions} -#{pr.deletions}
+      Updated: *#{moment(pr.updatedAt).fromNow()}*
+      Status: #{if pr.mergeable then "Mergeable" else "Unresolved Conflicts"}
+      Author: #{pr.user.login}
+      Assignee: #{if assignee then "#{assignee.name}" else "Unassigned"}
+    """
+
   listOpenPullRequestsForRoom = (room, user) ->
     repo = repos[room]
     if not repo
@@ -144,26 +179,15 @@
     .then (prs) ->
       return Promise.all prs.map (pr) ->
         return if not pr
-        author = lookupUserWithGithub pr.user
         assignee = lookupUserWithGithub pr.assignee
-        return Promise.all [ pr, author, assignee ]
+        return Promise.all [ pr, assignee ]
     .then (prs) ->
-      message = ""
-      for p in prs when p
-        pr = p[0]
-        author = p[1]
-        assignee = p[2]
-        message+= """
-          *<#{pr.htmlUrl}|#{pr.title}>* +#{pr.additions} -#{pr.deletions}
-          Updated: *#{moment(pr.updatedAt).fromNow()}*
-          Status: #{if pr.mergeable then "Mergeable" else "Unresolved Conflicts"}
-          Author: #{if author then "<@#{author.id}>" else "Unknown"}
-          Assignee: #{if assignee then "<@#{assignee.id}>" else "Unassigned"}
-          \n
-        """
-      if message.length is 0
+      attachments = []
+      attachments.push buildGithubAttachment p[0], p[1] for p in prs when p
+      if attachments.length is 0
         message = "No matching pull requests found"
-
+      else
+        message = attachments: attachments
       send message: room: room, message
     .catch ( error ) ->
       console.log error.stack


Add header to layout

Add header to layout

--- src/layout.coffee
+++ src/layout.coffee
@@ -1,5 +1,6 @@
 {my} = require './my'
 {render} = require './layout/render'
+{header} = require './layout/header'
 {controls} = require './layout/controls'
 {grid} = require './layout/grid'
 {inspector} = require './layout/inspector'
@@ -8,6 +9,7 @@
   _EXPORTS: ["render"]
   render: (world) -> render(world)
   _CHILDREN: [
+    header
     controls
     grid
     inspector


Updating this to @

Updating this to @

--- src/logger.coffee
+++ src/logger.coffee
@@ -18,7 +18,7 @@
       ]
     @logger.log = () ->
       args = arguments
-      winston.Logger.prototype.log.apply(this, args)
+      winston.Logger.prototype.log.apply(@, args)
     return @logger

 module.exports = Logger


:memo: Update docs for marker callback

:memo: Update docs for marker callback

--- src/marker.coffee
+++ src/marker.coffee
@@ -82,10 +82,14 @@
   #
   # * `callback` {Function} to be called when the marker changes.
   #   * `event` {Object} with the following keys:
-  #     * `oldHeadPosition` {Point} representing the former head position
-  #     * `newHeadPosition` {Point} representing the new head position
-  #     * `oldTailPosition` {Point} representing the former tail position
-  #     * `newTailPosition` {Point} representing the new tail position
+  #     * `oldHeadBufferPosition` {Point} representing the former head buffer position
+  #     * `newHeadBufferPosition` {Point} representing the new head buffer position
+  #     * `oldTailBufferPosition` {Point} representing the former tail buffer position
+  #     * `newTailBufferPosition` {Point} representing the new tail buffer position
+  #     * `oldHeadScreenPosition` {Point} representing the former head screen position
+  #     * `newHeadScreenPosition` {Point} representing the new head screen position
+  #     * `oldTailScreenPosition` {Point} representing the former tail screen position
+  #     * `newTailScreenPosition` {Point} representing the new tail screen position
   #     * `wasValid` {Boolean} indicating whether the marker was valid before the change
   #     * `isValid` {Boolean} indicating whether the marker is now valid
   #     * `hadTail` {Boolean} indicating whether the marker had a tail before the change


Fix autosubscribeNewUsers

Fix autosubscribeNewUsers

@req.temporary was missing, which caused weird things to happen.

Fixes #90.

--- src/router.coffee
+++ src/router.coffee
@@ -149,6 +149,7 @@
                                 req = Object.create(opts)
                                 req.operation = 'subscribe-node'
                                 req.node = "/user/#{userid}/#{type}"
+                                req.temporary = false
                                 req.writes = yes
                                 @run req
             else if opts.writes


router: rename frontend to backend

router: rename frontend to backend

--- src/router.coffee
+++ src/router.coffee
@@ -4,22 +4,22 @@

 class RemoteRouter
     constructor: () ->
-        @frontends = []
+        @backends = []
         # cache :: { userId: { queue: [Function], result: ... } }
         @cache = {}

-    addFrontend: (frontend) ->
-        @frontends.push frontend
+    addBackend: (backend) ->
+        @backends.push backend

     resolve: (userId, cb) ->
-        frontendIdx = 0
+        backendIdx = 0
         go = () =>
-            frontend = @frontends[frontendIdx]
-            unless frontend
+            backend = @backends[backendIdx]
+            unless backend
                 return cb new errors.NotFound("Cannot resolve user")
-            frontend.resolve userId, (error, result) ->
+            backend.resolve userId, (error, result) ->
                 if error
-                    frontendIdx++
+                    backendIdx++
                     go()
                 else
                     cb null, result
@@ -35,8 +35,8 @@
         @operations = require('./local/operations')
         @operations.setModel model

-    addFrontend: (frontend) ->
-        @remote.addFrontend frontend
+    addBackend: (backend) ->
+        @remote.addBackend backend

     resolve: (userId, cb) ->
         # First, look if already subscribed, therefore database is up to date:
@@ -46,7 +46,7 @@
             else if !error || error.constructor is errors.NotFound
                 # Otherwise, fan out to remote service
                 @remote.resolve userId, cb
-                # TODO: catch if remote frontend found ourselves
+                # TODO: catch if remote backend found ourselves
             else
                 cb error


Enable city filtering.

Enable city filtering.

--- src/router.coffee
+++ src/router.coffee
@@ -55,5 +55,7 @@
                 context.query = @processQuery fullUri.search(true)
                 if context.query.map?
                     p13n.setMapBackgroundLayer context.query.map
+                if context.query.city?
+                    p13n.set 'city', context.query.city
                 newArgs.push context
             @executeRoute callback, newArgs, context


fixed some subtle bugs

fixed some subtle bugs

--- src/sentai.coffee
+++ src/sentai.coffee
@@ -19,7 +19,7 @@
     eventCtxs = @events[eventName]
     if eventCtxs
       for eventCtx in eventCtxs
-        if eventCtx.cb.call(eventCtxs.ctx, payload) == false
+        if eventCtx.cb.call(eventCtx.ctx, payload) == false
           return false
     return true


Remove unnecessary code

Remove unnecessary code

--- src/server.coffee
+++ src/server.coffee
@@ -132,32 +132,6 @@
     paths.push(dir)
   paths

-# # nodeModulePackageMain: Look in moduleDir for packages.json
-# # Load it and read the 'main' key to get the file.
-# # Return this file unioned with the original moduleDir
-# # Return null if package.json doesn't eixst or dir doesn't exist
-# nodeModulePackageMain = (moduleDir) ->
-#   console.log "nodeModulePackageMain called"
-#   # moduleDir = '/full/path/node_modules/module-name'
-#   console.log "moduleDir: ", moduleDir
-#   # moduleName = 'module-name'
-#   moduleName = path.basename moduleDir
-#   console.log "moduleName: ", moduleName
-
-#   # packageFile = '/full/path/node_modules/module-name/packages.json'
-#   packageFile = path.join moduleDir, 'packages.json'
-#   console.log "packageFile: ", packageFile
-#   try
-#     file = readFileSync packageFile
-#     if _.isString file
-#       obj = JSON.parse file
-#       if obj?
-#         main = obj.main
-#         console.log "main: ", main
-#         return main
-#   catch e
-#   null
-
 # Recursively get final link path
 resolveLink = (linkPath, out) ->
   try
@@ -878,7 +852,6 @@
   # To ourselves oj is local but to them oj is native.
   # Removing this cache record ensures only the native copy
   # is saved to the client.
-  delete cache.files[require.resolve './index.js']
   delete cache.files[require.resolve '../lib/oj.js']

   return cache


Reference TLSSocket instead of underlying Socket

--- src/server.coffee
+++ src/server.coffee
@@ -271,7 +271,7 @@
       httpsServer.on 'clientError', (err) ->
         logger.error "An httpsServer clientError occured: #{err}"

-      httpsServer.on 'connection', (socket) -> trackConnection activeHttpsConnections, socket
+      httpsServer.on 'secureConnection', (socket) -> trackConnection activeHttpsConnections, socket

     return deferred.promise

@@ -318,7 +318,7 @@
         logger.info "API HTTPS listening on port " + apiPort
         ensureRootUser -> deferred.resolve()

-      apiHttpsServer.on 'connection', (socket) -> trackConnection activeApiConnections, socket
+      apiHttpsServer.on 'secureConnection', (socket) -> trackConnection activeApiConnections, socket

     return deferred.promise


Allow to save drafts in tests accounts

Allow to save drafts in tests accounts

--- server/controllers/messages.coffee
+++ server/controllers/messages.coffee
@@ -290,10 +290,10 @@
     # Add the message to draft or sent folder (imap)
     if message.isDraft
         steps.push (cb) ->
+            destination = draftBox
             log.debug "send#add_to_draft"
             account.imap_createMail draftBox, message, (err, uid) ->
                 return cb err if err
-                destination = draftBox
                 uidInDest = uid
                 cb null

@@ -301,10 +301,10 @@
     else
         log.debug "send#add_to_sent"
         steps.push (cb) ->
+            destination = sentBox
             # check if message already created by IMAP/SMTP (gmail)
             sentBox.imap_createMailNoDuplicate account, message, (err, uid) ->
                 return cb err if err
-                destination = sentBox
                 uidInDest = uid
                 cb null

@@ -314,6 +314,8 @@
         message.text = message.content
         delete message.attachments_backup
         delete message.content
+        uidInDest = 0 if account.isTest()
+        console.log destination.id, uidInDest
         message.mailboxIDs = {}
         message.mailboxIDs[destination.id] = uidInDest
         message.date = new Date().toISOString()


remove useless log

remove useless log

--- server/utils/socket_handler.coffee
+++ server/utils/socket_handler.coffee
@@ -67,7 +67,6 @@

 inScope = (socket, data) ->
-    log.debug "inscope", socket.scope_mailboxID, Object.keys data.mailboxIDs
     (socket.scope_mailboxID in Object.keys data.mailboxIDs) and
     socket.scope_before < data.date


Add comments to Spellbook.headingLinks

Add comments to Spellbook.headingLinks


Reattach language click

Reattach language click

--- components/layout/index.coffee
+++ components/layout/index.coffee
@@ -27,10 +27,11 @@
   render(DEFAULT_LANGUAGE)

   toggleLanguageOnClick = (e) ->
+    e.preventDefault()
     language = $(e.currentTarget).data('language')
     render(language)

-  $('body').on 'click', '.header-language-toggle a', toggleLanguageOnClick
+  $('body').on 'click', '.js-language-toggle', toggleLanguageOnClick

   initSlideShow()


Put client fetching in a different function; throw an error when #fetch is called by the client

--- models.coffee
+++ models.coffee
@@ -877,19 +877,21 @@
                         instanceList.push null
                 instanceList

+            clientFetch: (selector = {}, options = {}) ->
+                # trick to fetch a model anyway on the client side
+                autoVar = J.AutoVar -> modelClass.fetch selector, options
+                try
+                    return autoVar.get()
+                catch e
+                    if e instanceof J.VALUE_NOT_READY
+                        console.log "Try fetching again."
+                        return
+                    throw e
+
             fetch: (selector = {}, options = {}) ->
                 if Meteor.isClient and not Tracker.active
-                    # trick to fetch a model anyway on the client side
-                    autoVar = J.AutoVar -> modelClass.fetch selector, options
-                    try
-                        return autoVar.get()
-                    catch e
-                        if e instanceof J.VALUE_NOT_READY
-                            console.log "Try fetching again."
-                            return
-                        throw e
-                    # throw new Error "On the client, must call #{modelName}.fetch
-                    #     from a reactive computation."
+                    throw new Error "On the client, must call #{modelName}.fetch
+                        from a reactive computation."

                 if selector instanceof J.Dict
                     selector = selector.toObj()


Make inline tags inline-block

Make inline tags inline-block

Fixes #61

--- src/core/renderer.coffee
+++ src/core/renderer.coffee
@@ -33,6 +33,13 @@
   '.editor-container ol'         : { 'margin': '0 0 0 2em', 'padding': '0', 'list-style-type': 'decimal' }
   '.editor-container ul'         : { 'margin': '0 0 0 2em', 'padding': '0', 'list-style-type': 'disc' }

+# Make inline tags inline-block
+tags = _.reduce(Normalizer.TAGS, (tags, tag) ->
+  tags.push(".editor-container #{tag.toLowerCase()}") unless dom.BLOCK_TAGS[tag]? or dom.VOID_TAGS[tag]?
+  return tags
+, [])
+DEFAULT_STYLES[tags.join(', ')] = { 'display': 'inline-block' }
+
 LIST_STYLES = ['decimal', 'lower-alpha', 'lower-roman']
 rule = '.editor-container ol > li'
 _.each([1..9], (i) ->
@@ -40,6 +47,7 @@
   DEFAULT_STYLES[rule] = { 'list-style-type': LIST_STYLES[i%3] }
   rule += ' > li'
 )
+
 DEFAULT_STYLES[dom.DEFAULT_BREAK_TAG] = { 'display': 'none' } if dom.isIE(10)


修正xCreate新出现的BUG

修正xCreate新出现的BUG

--- src/core/x-create.coffee
+++ src/core/x-create.coffee
@@ -2,6 +2,12 @@
 oldIE = `!-[1,]`

 $.xCreate = xCreate = (template, context) ->
+
+	isSimpleValue = (value) ->
+		if value == null or value == undefined then return true
+		type = typeof value
+		return type isnt "object" and type isnt "function" or value instanceof Date
+
 	if template instanceof Array
 		elements = []
 		for part in template
@@ -33,16 +39,16 @@

 	content = template.content
 	if content?
-		if typeof content == "string"
-			if content.charAt(0) == '^'
+		if isSimpleValue(content)
+			if typeof content == "string" and content.charAt(0) == '^'
 				appendChild(el, document.createElement(content.substring(1)))
 			else
 				$el.text(content)
 		else
 			if content instanceof Array
 				for part in content
-					if typeof part == "string"
-						if part.charAt(0) == '^'
+					if isSimpleValue(part)
+						if typeof part == "string" and part.charAt(0) == '^'
 							appendChild(el, document.createElement(part.substring(1)))
 						else
 							appendChild(el, document.createTextNode(part))


Perform initial spliceInput exactly as it occurred in the buffer

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -52,15 +52,15 @@

   bufferDidChange: (change) ->
     {oldRange, newRange} = change
-
-    startBufferRow = oldRange.start.row
-    oldEndBufferRow = oldRange.end.row + 1
-    newEndBufferRow = newRange.end.row + 1
-
-    {start, replacedExtent} = @patch.spliceInput(Point(startBufferRow, 0), Point(oldEndBufferRow - startBufferRow, 0), Point(newEndBufferRow - startBufferRow, 0))
-    replacedExtent = Point(replacedExtent.row + 1, 0) if replacedExtent.column > 0
-
-    newOutputEnd = @computeTransformation(startBufferRow, newEndBufferRow)
+    {start, replacedExtent} = @patch.spliceInput(
+      oldRange.start,
+      traversal(oldRange.end, oldRange.start),
+      traversal(newRange.end, oldRange.start)
+    )
+    start = Point(start.row, 0)
+    replacedExtent = Point(replacedExtent.row + 1, 0)
+
+    newOutputEnd = @computeTransformation(oldRange.start.row, newRange.end.row + 1)
     replacementExtent = traversal(newOutputEnd, start)
     @emitter.emit 'did-change-text-sync', {start, replacedExtent, replacementExtent}


Move mega checking code of getTierCallback to a method.

--- js/app.coffee
+++ js/app.coffee
@@ -48,9 +48,7 @@

   getTierCallback = (tier) =>
     (pokemon) =>
-      unless @megas
-        return false if pokemon.mega and @tiers[pokemon.mega.tier] >= @tiers[tier]
-      @tiers[pokemon.tier] >= @tiers[tier] and not pokemon.legendary
+      @tiers[pokemon.tier] >= @tiers[tier] and not pokemon.legendary and @checkMega tier

   @checkMega = (pokemon, tier = 'OU') ->
     if @megas


coffelint fix

coffelint fix

--- app/assets/javascripts/app/controllers/widget/online_notification.coffee
+++ app/assets/javascripts/app/controllers/widget/online_notification.coffee
@@ -113,7 +113,7 @@
       processData: true
     )

-  updateHeight: =>
+  updateHeight: ->
     # set height of notification popover
     notificationsContainer  = $('.js-notificationsContainer')
     heightApp               = $('#app').height()


Forcefully focus on Tuesday for now.

Forcefully focus on Tuesday for now.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


mp4: Fix getNumSamplesInChunk()

mp4: Fix getNumSamplesInChunk()

--- mp4.coffee
+++ mp4.coffee
@@ -1198,7 +1198,7 @@
       if not entry.numChunks?
         # TOOD: too heavy
         sttsBox = @findParent('stbl').find 'stts'
-        return sttsBox.getTotalSamples() - @getNumSamplesExceptLastChunk()
+        return entry.samplesPerChunk
       if chunk < entry.firstChunk + entry.numChunks
         return entry.samplesPerChunk
     throw new Error "Chunk not found: #{chunk}"


Fix wrong votes indicator

Fix wrong votes indicator

It was confusing with the float right.

--- resources/assets/coffee/react/contest/voting/vote-summary.coffee
+++ resources/assets/coffee/react/contest/voting/vote-summary.coffee
@@ -18,14 +18,14 @@

 {div, span} = ReactDOMFactories

-baseClass = osu.classWithModifiers('contest__voting-star', ['smaller', 'float-right'])
+baseClass = osu.classWithModifiers('contest__voting-star', ['smaller'])
 selectedClass = 'contest__voting-star--selected'

 Contest.Voting.VoteSummary = ({voteCount, maxVotes}) ->
   div null,
     for i in [0...maxVotes]
       className = baseClass
-      className += " #{selectedClass}" if i >= voteCount
+      className += " #{selectedClass}" if i < voteCount

       div
         key: "vote-#{i}"


ArrayPort should only send each beginGroup once.

--- src/lib/ArrayPort.coffee
+++ src/lib/ArrayPort.coffee
@@ -12,7 +12,7 @@
         if socketId is null
             @sockets.forEach (socket) ->
                 socket.disconnect()
-            return
+            return

         unless @sockets[socketId]
             throw new Error "No socket '#{socketId}' available"
@@ -30,7 +30,7 @@

         return @sockets[socketId].beginGroup group if @isConnected socketId

-        @sockets[socketId].on "connect", =>
+        @sockets[socketId].once "connect", =>
             @sockets[socketId].beginGroup group
         @sockets[socketId].connect()

@@ -64,7 +64,7 @@
         if socketId is null
             @sockets.forEach (socket) ->
                 socket.disconnect()
-            return
+            return

         return unless @sockets[socketId]
         @sockets[socketId].disconnect()


Compile nu.nl javascript and css

Compile nu.nl javascript and css

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -69,10 +69,14 @@
           'build/js/loader/loader_bookmarklet.js'
         ]
         dest: 'build/factlink_loader_bookmarklet.js'
+      publisher:
+        src:  'build/js/publisher_poc/nu.js'
+        dest: 'build/nu.js'
     less:
       build:
         files:
           'build/css/basic.css': 'app/css/basic.less'
+          'build/css/publisher_poc/nu.nl.css': 'app/css/publisher_poc/nu.nl.less'
     cssmin:
       build:
         options:


Add `iron-collapse` snippet.

Add `iron-collapse` snippet.

--- snippets/iron-elements.cson
+++ snippets/iron-elements.cson
@@ -9,6 +9,13 @@
       url="$3"
       handle-as="json"
       on-response="handleResponse"></iron-ajax>
+    """
+  "iron-collapse":
+    "prefix": "iron-collapse"
+    "body": """
+    <iron-collapse id="$1">
+      $2
+    </iron-collapse>
     """
   "iron-form":
     "prefix": "iron-form"


style(): avoid deferred anti-pattern

style(): avoid deferred anti-pattern

--- src/madlib-locale.coffee
+++ src/madlib-locale.coffee
@@ -116,47 +116,49 @@
                 return Q.reject( error )

-            deferred = Q.defer()
-
             # Check if the locale is in the cache
             #
             if @cache[ locale ]?
+
                 @locale = @cache[ locale ]
                 @polyglot.locale(  objectUtils.getValue( 'name',    @locale, '??' ) )
                 @polyglot.replace( objectUtils.getValue( 'phrases', @locale, {}   ) )

-                deferred.resolve()
-            else
-                # Load the new locale phrases
-                #
-                xhr = new XHR( settings )
-                xhr.call(
+                return Q( @locale )
+
+            # Load the new locale phrases
+            #
+            loaded =
+
+                new XHR( settings ).call(
+
                     url:    "#{@localeLocation}/#{locale}.json"
                     type:   'json'
                     method: 'GET'
                 )
-                .then( ( data ) =>
-                    # Set polyglot locale and phrases on success
-                    #
-                    @locale = data.response

-                    @polyglot.locale(  objectUtils.getValue( 'name',    @locale, '??' ) )
-                    @polyglot.replace( objectUtils.getValue( 'phrases', @locale, {}   ) )
+            loaded.catch( () ->

-                    # Add the default locale to the cache
-                    #
-                    @cache[ @locale.name ] = @locale
+                console.error( "[i18n] Failed to load locale #{locale}")

-                    deferred.resolve()
+                return
+            )

-                ,   ( error ) ->
-                    console.error( "[i18n] Failed to load locale #{locale}")
-                    deferred.reject( error )
-                )
-                .done()
+            return loaded.then( ( data ) =>

-            return deferred.promise
+                # Set polyglot locale and phrases on success
+                #
+                @locale = data.response

+                @polyglot.locale(  objectUtils.getValue( 'name',    @locale, '??' ) )
+                @polyglot.replace( objectUtils.getValue( 'phrases', @locale, {}   ) )
+
+                # Add the default locale to the cache
+                #
+                @cache[ @locale.name ] = @locale
+
+                return @locale
+            )

         getLocaleName: () ->


Terminal: cycling channel when oskite fails to return the vm state

this was neccessary because at the time user makes the first request if oskite was off, client fails to retrieve any further messages even if oskite restarts.

--- client/Main/kite/oskite.coffee
+++ client/Main/kite/oskite.coffee
@@ -62,7 +62,11 @@
     @intervalId = KD.utils.repeat KD.config.osKitePollingMs, @bound 'fetchState'

   fetchState: ->
-    @vmInfo().then (@recentState) =>
+    @vmInfo().then (state) =>
+      @recentState = state
+      if state
+      then @emit 'vm.info.state', @recentState
+      else @cycleChannel() # backend's cycleChannel regressed - SY

   vmOn: ->
     if not @recentState? or @recentState.state is 'STOPPED'


Terminal: removed some accidental paste

Terminal: removed some accidental paste

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -107,7 +107,9 @@
       , =>
         KD.mixpanel "Open Webterm, fail", {vmName}
         KD.logToExternalWithTime "oskite: Can't open Webterm", vmName
-        @emit 'message', """https://koding.slack.com/files/sinan/F025B6R70/pasted_image_at_2014_02_20_05_34pm.png
+
+
+        @emit 'message', """

Couldn't connect to your VM.

Preparing your VM can take anywhere from


payment: remove paymentformmodal from includes

--- client/landing/includes.coffee
+++ client/landing/includes.coffee
@@ -170,7 +170,6 @@
   "app/MainApp/payment/paymentmethodchoice.coffee",
   "app/MainApp/payment/paymentform.coffee",
   "app/MainApp/payment/paymentchoiceform.coffee",
-  "app/MainApp/payment/paymentformmodal.coffee",
   "app/MainApp/payment/paymentdeleteconfirmationmodal.coffee",
   "app/MainApp/payment/vmproductview.coffee",
   "app/MainApp/payment/paymentworkflow.coffee",


Playing with coffee.

Playing with coffee.

--- enterprise/server/src/main/coffeescript/webadmin.coffee
+++ enterprise/server/src/main/coffeescript/webadmin.coffee
@@ -2,18 +2,13 @@
 from neo4j.webadmin import DashboardController
 from neo4j.webadmin.views import BaseView

-import lib.jquery
-import lib.underscore
-import lib.backbone
+from lib import jquery, underscore, backbone

-require ['neo4j/webadmin/DashboardController','neo4j/webadmin/views/BaseView',
-         'lib/jquery', 'lib/underscore','lib/backbone'],
-  (DashboardController, BaseView) ->
-    $(document).ready ->
-
-      baseview = new BaseView(el:$("body"))
-      baseview.render()
-
-      new DashboardController
-
-      Backbone.history.start()
+$(document).ready ->
+
+  baseview = new BaseView(el:$("body"))
+  baseview.render()
+
+  new DashboardController
+
+  Backbone.history.start()


Rename config file to 'brunch-config'

Rename config file to 'brunch-config'

with backward-compatible fallback and deprecation plan
Closes gh-686

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -312,13 +312,21 @@
   config._normalized = normalized
   config

-exports.loadConfig = (configPath = 'config', options = {}, callback) ->
-  fullPath = require.resolve sysPath.resolve configPath
-  delete require.cache[fullPath]
+exports.loadConfig = (configPath = 'brunch-config', options = {}, callback) ->
   try
+    # assign fullPath in two steps in case require.resolve throws
+    fullPath = sysPath.resolve configPath
+    fullPath = require.resolve fullPath
+    delete require.cache[fullPath]
     config = require(fullPath).config
   catch error
-    throw new Error "couldn\'t load config #{fullPath}. #{error}"
+    if configPath is 'brunch-config'
+      # start to warn about deprecation of 'config' with 1.8 release
+      # seamless and silent fallback until then
+      return exports.loadConfig 'config', options, callback
+      # 'config' should remain available as a working deprecated option until 2.0
+    else
+      throw new Error "couldn\'t load config #{fullPath}. #{error}"

   setConfigDefaults config, configPath
   warnAboutConfigDeprecations config


Remove duplicated options, and supporting deletion of previous option

--- lib/cl.coffee
+++ lib/cl.coffee
@@ -34,20 +34,18 @@

 # Parse Debug Options
 reDebug = /^-(-debug|d)$/i
-reDebugEx = /^--debug-extra=\w[\w-]*(,\w[\w-]*)*$/i
+ex = "\\w[\\w-]*"
+reDebugEx = new RegExp "^--debug-extra=#{ex}(,#{ex})*$", 'i'
+reDebugExD = new RegExp "^--debug-extra-d=#{ex}(,#{ex})*$", 'i'
+reExtra = new RegExp "#{ex}(?=,|$)", 'gi'
+exOpts = new Set
 parseDebugOptions = (arg) ->
-  if reDebug.test arg
-    process.env.DEBUG = 1
-    true
-  else if reDebugEx.test arg
-    if process.env.DEBUG_EXTRA?
-      process.env.DEBUG_EXTRA += ','
-    else
-      process.env.DEBUG_EXTRA = ''
-    process.env.DEBUG_EXTRA += arg.split("=")[1]
-    true
-  else
-    false
+  switch
+    when reDebug.test arg then process.env.DEBUG = 1
+    when reDebugEx.test arg then exOpts.add opt for opt in arg.match reExtra
+    when reDebugExD.test arg then exOpts.delete opt for opt in arg.match reExtra
+    else return false
+  true

 process.argv.forEach (arg, idx) ->
@@ -60,5 +58,6 @@

 if process.env.DEBUG?
   console.log "[DEBUG] Debug Mode Enabled".cyan
-if process.env.DEBUG_EXTRA?
+if exOpts.size > 0
+  process.env.DEBUG_EXTRA = Array.from(exOpts).join ','
   console.log "[DEBUG] Extra Options: #{process.env.DEBUG_EXTRA}".cyan


put the clsiserverid onto qs object for correct server allocation

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -36,9 +36,7 @@
 				_csrf: window.csrfToken
 			}

-		parseCompileResponse = (response) ->
-			if response.clsiServerId? and response.clsiServerId != ide.clsiServerId
-				ide.clsiServerId = response.clsiServerId
+		parseCompileResponse = (response) ->

 			# Reset everything
 			$scope.pdf.error      = false
@@ -72,14 +70,7 @@
 			else if response.status == "success"
 				$scope.pdf.view = 'pdf'
 				$scope.shouldShowLogs = false
-				# define the base url
-				$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
-				# add a query string parameter for the compile group
-				if response.compileGroup?
-					$scope.pdf.compileGroup = response.compileGroup
-					$scope.pdf.url = $scope.pdf.url + "&compileGroup=#{$scope.pdf.compileGroup}"
-				if response.clsiServerId?
-					$scope.pdf.url = $scope.pdf.url + "&clsiserverid=#{response.clsiServerId}"
+
 				# make a cache to look up files by name
 				fileByPath = {}
 				for file in response.outputFiles
@@ -98,6 +89,9 @@
 				if response.compileGroup?
 					$scope.pdf.compileGroup = response.compileGroup
 					qs.compileGroup = "#{$scope.pdf.compileGroup}"
+				if response.clsiServerId?
+					qs.clsiserverid = response.clsiServerId
+					ide.clsiServerId = response.clsiServerId
 				# convert the qs hash into a query string and append it
 				qs_args = ("#{k}=#{v}" for k, v of qs)
 				$scope.pdf.qs = if qs_args.length then "?" + qs_args.join("&") else ""


add warn()

add warn()

--- log.coffee
+++ log.coffee
@@ -3,7 +3,10 @@
 # Log info messages for debugging. **TODO**: empty later

 info = (message) ->
-  console.log message
+  console.log "INFO: #{message}"
+
+warn = (message) ->
+  console.log "WARN: #{message}"

 # Throw an application specific error.

@@ -16,4 +19,5 @@

 module.exports =
   info: info
+  warn: warn
   fail: fail


Test token when we ensure()

Test token when we ensure()

--- src/clientonlytoken.coffee
+++ src/clientonlytoken.coffee
@@ -45,54 +45,83 @@
   callback null

 ClientOnlyToken::expired = ->
-  !@expiresAt? or (Date.parse(@expiresAt) > (Date.now() + ONE_HOUR))
+  @expiresAt? and (Date.parse(@expiresAt) < (Date.now() + ONE_HOUR))

 ONE_HOUR = 1000 * 60 * 60
+
+ClientOnlyToken::test = (callback) ->
+
+  headers =
+    "Accept": JSON_TYPE
+    "Authorization": "Bearer #{@token}"
+
+  url = "https://api.producthunt.com/v1/posts?per_page=1"
+
+  web.get url, headers, (err, response, body) ->
+    if err && err.statusCode == 401
+      callback null, false
+    else if err
+      callback err
+    else
+      callback null, true
+
+ClientOnlyToken.newToken = (clientID, clientSecret, callback) ->
+
+  params =
+    client_id: clientID
+    client_secret: clientSecret
+    grant_type: "client_credentials"
+
+  payload = JSON.stringify params
+
+  headers =
+    "Accept": JSON_TYPE
+    "Content-Type": JSON_TYPE
+    "Content-Length": Buffer.byteLength payload
+
+  url = 'https://api.producthunt.com/v1/oauth/token'
+
+  web.post url, headers, payload, (err, response, body) ->
+    if err
+      callback err
+    else if response.statusCode != 200
+      code = response.statusCode
+      err = new Error("Status code #{code} posting to #{url}: #{body}")
+      callback err
+    else
+      results = JSON.parse(body)
+      if !cot?
+        cot = new ClientOnlyToken({clientID: clientID})
+      cot.token = results.access_token
+
+      # XXX: expiration in seconds or milliseconds?
+      if _.isNumber results.expires_in
+        expiresAt = new Date(Date.now() + (results.expires_in * 1000))
+        cot.expiresAt = expiresAt.toISOString()
+
+      cot.save (err) ->
+        if err
+          callback err
+        else
+          callback null, cot.token

 ClientOnlyToken.ensure = (clientID, clientSecret, callback) ->

   ClientOnlyToken.get clientID, (err, cot) ->
-    if err && err.name != "NoSuchThingError"
-      callback err
+    if err
+      if err.name is "NoSuchThingError"
+        ClientOnlyToken.newToken clientID, clientSecret, callback
+      else
+        callback err
+    else if cot.expired()
+      ClientOnlyToken.newToken clientID, clientSecret, callback
     else
-      if cot && !cot.expired()
-        callback null, cot.token
-      else
-        params =
-          client_id: clientID
-          client_secret: clientSecret
-          grant_type: "client_credentials"
-
-        payload = JSON.stringify params
-
-        headers =
-          "Accept": JSON_TYPE
-          "Content-Type": JSON_TYPE
-          "Content-Length": Buffer.byteLength payload
-
-        url = 'https://api.producthunt.com/v1/oauth/token'
-
-        web.post url, headers, payload, (err, response, body) ->
-          if err
-            callback err
-          else if response.statusCode != 200
-            code = response.statusCode
-            err = new Error("Status code #{code} posting to #{url}: #{body}")
-            callback err
-          else
-            results = JSON.parse(body)
-            if !cot?
-              cot = new ClientOnlyToken({clientID: clientID})
-            cot.token = results.access_token
-            # XXX: expiration in seconds or milliseconds?
-            if _.isNumber results.expires_in
-              expiresAt = new Date(Date.now() + (results.expires_in * 1000))
-              cot.expiresAt = expiresAt.toISOString()
-
-            cot.save (err) ->
-              if err
-                callback err
-              else
-                callback null, cot.token
+      cot.test (err, isValid) ->
+        if err
+          callback err
+        else if !isValid
+          ClientOnlyToken.newToken clientID, clientSecret, callback
+        else
+          callback null, cot.token

 module.exports = ClientOnlyToken


simplify prompt setting

simplify prompt setting

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -50,6 +50,7 @@
   destroyed: false
   items: []
   itemsByProvider: null # Used to cache result
+  lastNarrowQuery: ''

   onDidMoveToPrompt: (fn) -> @emitter.on('did-move-to-prompt', fn)
   emitDidMoveToPrompt: -> @emitter.emit('did-move-to-prompt')
@@ -120,7 +121,7 @@
   start: ->
     activatePaneItemInAdjacentPane(@editor, split: settings.get('directionToOpen'))
     @grammar.activate()
-    @setPromptLine((@input ? '') + "\n")
+    @setPrompt(@input)
     @moveToPrompt()
     @refresh()

@@ -231,7 +232,7 @@
     # In case prompt accidentaly mutated
     eof = @editor.getEofBufferPosition()
     if eof.isLessThan(@itemAreaStart)
-      eof = @setPromptLine("\n").end
+      eof = @setPrompt().end
       @moveToPrompt()

     filterSpec = getFilterSpecForQuery(@getNarrowQuery())
@@ -283,7 +284,7 @@
           for selection in @editor.getSelections() when onPrompt(selection.getBufferRange())
             selection.destroy()
           # Recover query on prompt
-          @setPromptLine(@lastNarrowQuery) if @lastNarrowQuery
+          @setPrompt(@lastNarrowQuery)
         else
           @refresh()

@@ -427,7 +428,9 @@
     @editor.bufferRangeForBufferRow(0)

   # Return range
-  setPromptLine: (text) ->
+  setPrompt: (text='') ->
+    if @editor.getLastBufferRow() is 0
+      text += "\n"
     @ignoreChangeOnEditor = true
     range = @editor.setTextInBufferRange(@getPromptRange(0), text)
     @ignoreChangeOnEditor = false


Add spec for `__proto__` entries

Add spec for `__proto__` entries

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -3822,6 +3822,14 @@
         expect(atom.project.getPaths()).toEqual [alphaDirPath, thetaDirPath]
         expect(document.querySelector('.placeholder')).not.toExist()

+  describe "when there is a __proto__ entry present", ->
+    it "does not break anything", ->
+      # No assertions needed - multiple exceptions will be thrown if this test fails
+      projectPath = temp.mkdirSync('atom-project')
+      protoPath = path.join(projectPath, "__proto__")
+      fs.writeFileSync(protoPath, 'test')
+      atom.project.setPaths([projectPath])
+
   describe "directory expansion serialization", ->
     it "converts legacy expansion serialization Objects to Maps", ->
       # The conversion actually happens when a new Directory


Don't try to destroy TreeView using Pane.close

Fixes failures in atom/atom#14221

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -270,7 +270,7 @@
       treeView = atom.workspace.getLeftDock().getActivePaneItem()
       treeViewHTML = treeView.element.outerHTML
       treeView.roots[0].collapse()
-      atom.workspace.getLeftDock().getActivePane().close()
+      treeView.destroy()

       waitForWorkspaceOpenEvent ->
         atom.commands.dispatch(atom.views.getView(atom.workspace), 'tree-view:toggle')


nitrogen6x.coffee: Promote to released

nitrogen6x.coffee: Promote to released

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- nitrogen6x.coffee
+++ nitrogen6x.coffee
@@ -6,7 +6,7 @@
 	aliases: [ 'nitrogen6x' ]
 	name: 'Nitrogen 6X'
 	arch: 'armv7hf'
-	state: 'experimental'
+	state: 'released'

 	instructions: commonImg.instructions
 	gettingStartedLink:


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -1,11 +1,6 @@
 test "Animation should default to first frame", ->
   animation = Animation.fromPixieId 45
-
-  timeoutId = setTimeout(checkActiveProperty, 5)
-
-  checkActiveProperty = (animation) ->
-    if animation.active
-      equals animation.active(), 0
-      clearTimeout(timeoutId)
+
+  equals animation.active(), 0