KodingAppsController: Uncomment commented out helper method to create app skeletons

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -114,829 +114,9 @@
           style      : "modal-cancel"
           callback   : -> modal.destroy()

-  ###
-
-  @manifests = {}
-
-  @getManifestFromPath = getManifestFromPath = (path)->
-    folderName = (p for p in path.split("/") when /\.kdapp/.test p)[0]
-    app        = null
-    return app unless folderName
-    for own name, manifest of KodingAppsController.manifests
-      do -> app = manifest  if manifest.path.search(folderName) > -1
-    return app
-
-  constructor:->
-
-    super
-
-    @_loadedOnce    = no
-    @appManager     = KD.getSingleton "appManager"
-    @vmController   = KD.getSingleton "vmController"
-    mainController  = KD.getSingleton "mainController"
-    @manifests      = KodingAppsController.manifests
-    @publishedApps  = {}
-    @_fetchQueue    = []
-    @appStorage     = KD.getSingleton('appStorageController').storage 'Finder', '1.1.1'
-    @watcher        = new AppsWatcher
-
-    # @fetchApps =>
-    #   @getPublishedApps()
-    #   @createExtensionToAppMap()
-    #   @fetchUserDefaultAppConfig()
-
-    @on "UpdateDefaultAppConfig", (extension, appName) =>
-      @updateDefaultAppConfig extension, appName
-
-    mainController.on "accountChanged.to.loggedIn", @bound "getPublishedApps"
-
-    #  - NewAppIsAdded
-    #  - FileIsRemoved
-    #  - AppIsRemoved
-    #  - FileHasChanged
-    #  - ManifestHasChanged
-
-    @watcher.on "NewAppIsAdded", (app, change)=>
-      manifestPath = "#{change.file.fullPath}/manifest.json"
-      manifest = FSHelper.createFileFromPath manifestPath
-      manifest.exists (err, exists)=>
-        return  unless exists
-        @fetchAppFromFs app, =>
-          @putAppsToAppStorage null, =>
-            @emit "UpdateAppData", app
-
-    @watcher.on "AppIsRemoved", (app, change)=>
-      @invalidateDeletedApps [app], no, =>
-        @emit "InvalidateApp", app
-
-    @watcher.on "ManifestHasChanged", (app, change)=>
-      @fetchAppFromFs app, =>
-        @putAppsToAppStorage null, =>
-          @emit "UpdateAppData", app
-
-  getAppPath:(manifest, escaped=no)->
-
-    path = if 'string' is typeof manifest then manifest else manifest.path
-    path = if /^~/.test path then "/home/#{KD.nick()}#{path.substr(1)}"\
-           else path
-    return FSHelper.escapeFilePath path  if escaped
-    return path.replace /(\/+)$/, ""
-
-  # #
-  # FETCHERS
-  # #
-
-  fetchApps:(callback)->
-
-    kb = (manifests)=>
-      callback null, manifests
-      @watcher._trackedApps = if manifests then Object.keys manifests else []
-
-    @appStorage.ready =>
-      manifests = @getManifests()
-      apps      = Object.keys manifests
-      if apps.length > 0 then kb manifests
-      else
-        @fetchAppsFromDb (err, apps)=>
-          if err
-            @fetchAppsFromFs (err, apps)=>
-              return callback? err  if err
-              kb apps
-          else if err
-            callback? err
-          else
-            kb apps
-
-  fetchAppFromFs:(appName, cb)->
-
-    manifests = @getManifests()
-    appsPath = "/home/#{KD.nick()}/Applications/"
-    suffix   = ".kdapp/manifest.json"
-
-    manifest = FSHelper.createFileFromPath "#{appsPath}#{appName}#{suffix}"
-    manifest.fetchContents (err, response)=>
-      # warn err  if err
-      return cb null  if err or not response
-
-      try
-        manifest = JSON.parse response
-        manifests[manifest.name] = manifest
-      catch e
-        warn "Manifest file is broken:", e
-        return cb null
-
-      @constructor.manifests = manifests
-      cb null
-
-  fetchAppsFromFsHelper:(apps, callback)->
-
-    stack = []
-    apps.forEach (app)=>
-      stack.push (cb)=> @fetchAppFromFs app, cb
-    async.parallel stack, callback
-
-  fetchAppsFromFs:(cb)->
-
-    @_fetchQueue.push cb  if cb
-    return if @_fetchQueue.length > 1
-
-    @watcher.watch KD.utils.getTimedOutCallback (err, files)=>
-      @_loadedOnce = yes
-      if err or not Array.isArray files or files.length is 0
-        @putAppsToAppStorage {}
-        callback()  for callback in @_fetchQueue
-        @_fetchQueue = []
-      else
-        apps = @filterAppsFromFileList files
-        @fetchAppsFromFsHelper apps, (result)=>
-          @putAppsToAppStorage()
-          for callback in @_fetchQueue
-            callback null, @getManifests()
-          @_fetchQueue = []
-    , =>
-      warn msg = "Timeout reached for kite request"
-      KD.logToExternalWithTime msg
-      callback() for callback in @_fetchQueue
-      @_fetchQueue = []
-    , KD.config.fileFetchTimeout
-
-  fetchAppsFromDb:(callback)->
-
-    @appStorage.fetchValue 'apps', (apps)=>
-      @putDefaultShortcutsBack =>
-        if apps and Object.keys(apps).length > 0
-          @constructor.manifests = apps
-          callback null, apps
-        else
-          callback new Error "There are no apps in the app storage."
-
-  syncAppStorageWithFS:(force=no, callback=noop)->
-
-    currentApps = Object.keys(@getManifests())
-
-    # log "Synchronizing AppStorage with FileSystem..."
-
-    @watcher.watch (err, files)=>
-
-      return warn err  if err
-
-      existingApps = @filterAppsFromFileList files
-      newApps      = (app for app in existingApps when app not in currentApps) or []
-      removedApps  = (app for app in currentApps  when app not in existingApps) or []
-
-      # log "APPS FOUND IN AppStorage:", currentApps
-      # log "APPS FOUND IN FS:", existingApps
-      # log "REMOVED APPS:", removedApps
-      # log "NEW APPS:", newApps
-
-      # log "Nothing changed"  if removedApps.length is 0 and \
-      #                           newApps.length is 0
-
-      @invalidateDeletedApps removedApps, force, =>
-        # log "DELETED APPS REMOVED FROM APPSTORAGE"
-        appsToFetch = if force then existingApps else newApps
-        # log "FOLLOWING APPS WILL BE FETCHED", appsToFetch
-        @fetchAppsFromFsHelper appsToFetch, =>
-          # log "APPS FETCHED"
-          @emit "AppsDataChanged", {removedApps, newApps, existingApps, force}
-
-      @_loadedOnce = yes
-      callback?()
-
-  filterAppsFromFileList:(files)->
-    return (file.name.replace /\.kdapp$/, '' \
-            for file in files when (/\.kdapp$/.test file.name) \
-            and file.type is 'folder')
-
-  fetchUpdateAvailableApps: (callback, force) ->
-    return callback? null, @updateAvailableApps  if @updateAvailableApps and not force
-    {publishedApps}      = @
-    @updateAvailableApps = []
-
-    @fetchApps (err, apps) =>
-      for own appName, app of apps
-        if @isAppUpdateAvailable app.name, app.version
-          @updateAvailableApps.push publishedApps[app.name]
-      callback? null, @updateAvailableApps
-
-  fetchCompiledAppSource:(manifest, callback)->
-
-    indexJs = FSHelper.createFileFromPath "#{@getAppPath manifest}/index.js"
-    indexJs.fetchContents callback
-
-  # #
-  # MISC
-  # #
-
-  removeShortcut:(shortcut, callback)->
-    @appStorage.fetchValue 'shortcuts', (shortcuts)=>
-      delete shortcuts[shortcut]
-      @appStorage.setValue 'shortcuts', shortcuts, (err)->
-        callback err
-
-  putDefaultShortcutsBack:(callback)->
-    # if @appStorage.getValue 'shortcuts'
-    #   return  @utils.defer -> callback null
-
-    # @appStorage.reset()
-    @appStorage.setValue 'shortcuts', defaultShortcuts, (err)->
-      callback? err
-
-  putAppsToAppStorage:(apps, callback)->
-    # warn "calling putAppsToAppStorage:", apps
-    apps or= @getManifests()
-    @constructor.manifests = apps
-    @appStorage.setValue 'apps', apps, (err)-> callback? err
-
-  invalidateDeletedApps:(deletedApps, force=no, callback)->
-    # log "REQUESTED TO INVALIDATE:", deletedApps
-    return if force
-      @constructor.manifests = {}
-      @putAppsToAppStorage manifests, callback
-
-    manifests = @getManifests()
-    delete manifests[app]  for app in deletedApps
-    if deletedApps.length > 0
-      @putAppsToAppStorage manifests, callback
-    else
-      callback null
-
-  defineApp:(name, script)->
-    KD.registerAppScript name, script if script
-
-  getAppScript:(manifest, callback = noop)->
-
-    {name} = manifest
-
-    if script = KD.getAppScript name
-      callback null, script
-    else
-      @fetchCompiledAppSource manifest, (err, script)=>
-        if err
-          @compileApp name, callback
-        else
-          @defineApp name, script
-          callback err, script
-
-  getPublishedApps: (callback) ->
-    # return unless KD.isLoggedIn()
-    appNames = (appName for own appName, manifest of @getManifests()) or []
-    query    = "manifest.name": "$in": appNames
-    {JNewApp}   = KD.remote.api
-    JNewApp.fetchAllAppsData query, (err, apps)=>
-      @publishedApps = map = {}
-      apps?.forEach (app) =>
-        map[app.manifest.name] = new JNewApp app
-      @emit "UserAppModelsFetched", map
-      callback? map
-
-  isAppUpdateAvailable: (appName, appVersion) ->
-    if @publishedApps[appName]
-      return @utils.versionCompare appVersion, "lt", @publishedApps[appName].manifest.version
-
-  getAppUpdateType: (appName) ->
-    app = @publishedApps[appName]
-    return null unless app
-    return if app.manifest.forceUpdate is yes then "required" else "available"
-
-  updateAllApps:->
-    @fetchUpdateAvailableApps (err, apps) =>
-      return warn err  if err
-      stack = []
-      delete @notification
-      apps?.forEach (app) =>
-        stack.push (callback) =>
-          @updateUserApp app.manifest, callback
-      async.series stack
-
-  # added this to keep backward compatibility
-  refreshApps: (callback) -> @syncAppStorageWithFS yes, callback
-
-  updateUserApp:(manifest, callback)->
-    appName = manifest.name
-    unless @notification
-      @notification = new KDNotificationView
-        type        : "mini"
-        title       : "Updating #{appName}: Deleting old app files"
-        duration    : 10000
-
-    folder = FSHelper.createFileFromPath manifest.path, "folder"
-    folder.remove (err, res) =>
-      if err
-        @notification.setClass "error"
-        @notification.notificationSetTitle "An error occured while updating #{appName}."
-        return no
-      @refreshApps =>
-        @notification.notificationSetTitle "Updating #{appName}: Fetching new app details"
-        KD.remote.api.JNewApp.someWithRelationship { "manifest.name": appName }, {}, (err, app) =>
-          @notification.notificationSetTitle "Updating #{appName}: Updating app to latest version"
-          @installApp app[0], app[0].versions.last, =>
-            @refreshApps()
-            callback?()
-            @emit "AnAppHasBeenUpdated"
-            @notification.notificationSetTitle "#{appName} has been updated successfully"
-      , yes
-
-  # #
-  # KITE INTERACTIONS
-  # #
-
-  hasForceUpdate: (appInstance) ->
-    manifest                 = @constructor.manifests[appInstance.getOptions().name]
-    {devMode, name, version} = manifest
-    forceUpdate              = @getAppUpdateType(name) is "required"
-    updateAvailable          = @isAppUpdateAvailable(name, version)
-    hasUpdate                = updateAvailable and not devMode and forceUpdate
-
-    @showUpdateRequiredModal manifest  if hasUpdate
-    return hasUpdate
-
-  putAppResources: (appInstance) ->
-    return  unless appInstance
-
-    manifest = appInstance.getOptions()
-    return  unless manifest.thirdParty
-
-    appView = appInstance.getView()
-    appView.addSubView loader = new KDLoaderView
-      loaderOptions :
-        color       : "#ff9200"
-        speed       : 2
-        range       : 0.7
-        density     : 60
-      cssClass      : "app-loading"
-      size          :
-        width       : 128
-
-    appView.once "viewAppended", -> loader.show()
-
-    putStyleSheets manifest
-
-    @getAppScript manifest, (err, appScript)=>
-      return warn err  if err
-
-      loader.destroy()
-      id = appView.getId()
-
-      try
-        # security please!
-        do (appView)->
-          eval "var appView = KD.instances[\"#{id}\"];\n\n" + appScript
-      catch error
-        # if not manifest.ignoreWarnings? # GG FIXME
-        showError error
-
-
-  publishApp:(path, callback)->
-
-    if not (KD.checkFlag('app-publisher') or KD.checkFlag('super-admin'))
-      err = "You are not authorized to publish apps."
-      log err
-      callback? err
-      return no
-
-    manifest = getManifestFromPath(path)
-    appName  = manifest.name
-
-    notification = new KDNotificationView
-      overlay       :
-        transparent : no
-        destroyOnClick: no
-      loader        :
-        color       : "#ffffff"
-      title         : "Please wait while we are publishing your app..."
-      followUps     :
-        duration    : 10000
-        title       : "We are still working on it. Your app will be published soon..."
-      duration      : 120000
-
-    @getAppScript manifest, (appScript)=>
-
-      manifest   = @getManifest appName
-      appPath    = @getAppPath manifest
-      options    =
-        method   : "app.publish"
-        withArgs : {appPath}
-
-      @vmController.run options, (err, res)=>
-        if err
-          warn err
-          notification.destroy()
-          callback? err
-        else
-          manifest.authorNick = KD.whoami().profile.nickname
-          jAppData     =
-            title      : manifest.name        or "Application Title"
-            body       : manifest.description or "Application description"
-            identifier : manifest.identifier  or "com.koding.apps.#{__utils.slugify manifest.name}"
-            manifest   : manifest
-
-          notification.destroy()
-
-          @createApp jAppData, (err, app) =>
-            if err
-              warn err
-              return callback? err
-            @appManager.open "Apps"
-            @appManager.tell "Apps", "updateApps"
-            callback?()
-
-  createApp:(formData, callback)->
-    KD.remote.api.JNewApp.create formData, (err, app)->
-      callback? err, app
-
-  compileApp:(name, callback)->
-
-    compileOnServer = (app)=>
-      return warn "#{name}: No such application!" unless app
-      appPath = @getAppPath app
-
-      loader = new KDNotificationView
-        duration : 18000
-        title    : "Compiling #{name}..."
-        type     : "mini"
-
-      @vmController.run "kdc #{appPath}", (err, response)=>
-        if not err
-          nickname    = KD.nick()
-          publishPath = "/home/#{nickname}/Web/.applications"
-          @vmController.run
-            method      : "fs.createDirectory"
-            withArgs    :
-              path      : publishPath
-              recursive : yes
-          , (err, response)=>
-            loader.notificationSetTitle "Publishing app static files..."
-            linkFile = "#{publishPath}/#{KD.utils.slugify name}"
-            @vmController.run "rm #{linkFile}; ln -s #{appPath} #{linkFile}", (err, response)=>
-              loader.notificationSetTitle "Fetching compiled app..."
-              @fetchCompiledAppSource app, (err, res)=>
-                if not err
-                  @defineApp name, res
-                  loader.notificationSetTitle "App compiled successfully"
-                  loader.notificationSetTimer 2000
-                callback? err, res
-        else
-          loader.destroy()
-
-          if err.message is "exit status 127"
-            modal = new ModalViewWithTerminal
-              title   : "Koding app compiler is not installed in your VM."
-              width   : 500
-              overlay : yes
-              terminal:
-                hidden: yes
-              content : """
-

-

-                            If you want to install it now, click Install Compiler button.
-

-

-                            Remember to enter your password when asked.
-

-

-                        """
-              buttons:
-                "Install Compiler":
-                  cssClass: "modal-clean-green"
-                  callback: =>
-                    modal.run "sudo npm install -g kdc; echo $?|kdevent;" # find a clean/better way to do it.
-
-            modal.on "terminal.event", (data)->
-              if data is "0"
-                new KDNotificationView title: "Installed successfully!"
-                modal.destroy()
-              else
-                new KDNotificationView
-                  title   : "An error occured."
-                  content : "Something went wrong while installing Koding App Compiler. Please try again."
-
-            callback? err
-            return
-
-          if response
-            details = """
#{response}
"""
-          else
-            details = ""
-
-          new KDModalView
-            title   : "An error occured while compiling the App!"
-            width   : 500
-            overlay : yes
-            content : """
-

-
#{err.message}

-                        #{details}
-

-                      """
-          callback? err
-
-    unless @getManifest name
-      @fetchApps (err, apps)->
-        compileOnServer apps[name]
-    else
-      compileOnServer @getManifest name
-
-  getManifests:->
-    @constructor.manifests
-
-  getManifest:(appName)->
-    @constructor.manifests[appName]
-
-  installApp:(app, version, callback)->
-
-    # add group membership control when group based apps feature is implemented!
-    KD.requireMembership
-      onFailMsg : "Login required to install Apps"
-      onFail    : => callback yes
-      callback  : => @fetchApps (err, manifests = {})=>
-
-        KD.showError err,
-          KodingError : 'Something went wrong while fetching apps'
-        return callback? err  if err
-
-        if app.title in Object.keys(manifests)
-          new KDNotificationView
-            type : "mini", title : "App is already installed!"
-          callback? yes
-        else
-          if app.approved isnt yes and not KD.checkFlag 'app-publisher'
-            KD.showError "This app is not approved, installation cancelled."
-            callback? err
-          else
-            app.fetchCreator (err, acc)=>
-              KD.showError err,
-                KodingError : 'Failed to fetch app creator info'
-              return callback? err  if err
-
-              KD.mixpanel "Install Application, success", app.manifest.identifier
-
-              @vmController.run
-                method       : "app.install"
-                withArgs     :
-                  owner      : acc.profile.nickname
-                  identifier : app.manifest.identifier
-                  appPath    : @getAppPath app.manifest
-                  version    : version
-              , (err, res)=>
-                if err
-                  KD.showError err,
-                    KodingError: """Something wrong with Apps server,
-                                    please try again later"""
-                  return callback?()
-                app.install (err)=>
-                  KD.showError err
-                  @appManager.open "StartTab"
-                  callback?()
-
   # #
   # MAKE NEW APP
   # #
-
-  newAppModal = null
-
-  makeNewApp:(callback)->
-
-    return callback? yes if newAppModal
-
-    newAppModal = new KDModalViewWithForms
-      title                       : "Create a new Application"
-      content                     :
-        """

-            Please select the application type you want to start with.
-            Alternatively, you can modify an existing app; all applications are installed under ~/Applications folder,
-            you can right click on any of them, go to Applications menu, and click Download source files and start
-            reading manifest.json
-

-        """
-      overlay                     : yes
-      width                       : 400
-      height                      : "auto"
-      tabs                        :
-        navigable                 : yes
-        forms                     :
-          form                    :
-            buttons               :
-              Create              :
-                cssClass          : "modal-clean-gray"
-                loader            :
-                  color           : "#444444"
-                  diameter        : 12
-                callback          : =>
-                  unless newAppModal.modalTabs.forms.form.inputs.name.validate()
-                    newAppModal.modalTabs.forms.form.buttons.Create.hideLoader()
-                    return
-                  name        = newAppModal.modalTabs.forms.form.inputs.name.getValue()
-                  type        = newAppModal.modalTabs.forms.form.inputs.type.getValue()
-                  name        = name.replace(/[^a-zA-Z0-9\/\-.]/g, '')  if name
-                  manifestStr = defaultManifest type, name
-                  manifest    = JSON.parse manifestStr
-                  appPath     = @getAppPath manifest
-
-                  # FIXME Use default VM ~ GG
-                  FSHelper.exists appPath, null, (err, exists)=>
-                    if exists
-                      newAppModal.modalTabs.forms.form.buttons.Create.hideLoader()
-                      new KDNotificationView
-                        type      : "mini"
-                        cssClass  : "error"
-                        title     : "App folder with that name is already exists, please choose a new name."
-                        duration  : 3000
-                    else
-                      @prepareApplication {isBlank : type is "blank", name}, (err, response)=>
-                        callback? err
-                        newAppModal.modalTabs.forms.form.buttons.Create.hideLoader()
-                        newAppModal.destroy()
-
-            fields                :
-              type                :
-                label             : "Type"
-                itemClass         : KDSelectBox
-                type              : "select"
-                name              : "type"
-                defaultValue      : "sample"
-                selectOptions     : [
-                  { title : "Sample Application", value : "sample" }
-                  { title : "Blank Application",  value : "blank"  }
-                ]
-              name                :
-                label             : "Name:"
-                name              : "name"
-                placeholder       : "name your application..."
-                validate          :
-                  rules           :
-                    regExp        : /^[a-z\d]+([-][a-z\d]+)*$/i
-                  messages        :
-                    regExp        : "For Application name only lowercase letters and numbers are allowed!"
-
-    newAppModal.once "KDObjectWillBeDestroyed", ->
-      newAppModal = null
-      callback? yes
-
-  _createChangeLog:(name)->
-    today = new Date().format('yyyy-mm-dd')
-    {profile} = KD.whoami()
-    fullName  = KD.utils.getFullnameFromAccount()
-
-    """
-     #{today} #{fullName} <@#{profile.nickname}>
-
-        * #{name} (index.coffee): Application created.
-    """
-
-  prepareApplication:({isBlank, name}, callback)->
-
-    type         = if isBlank then "blank" else "sample"
-    name         = if name is "" then null else name
-    name         = name.replace(/[^a-zA-Z0-9\/\-.]/g, '')  if name
-    manifestStr  = defaultManifest type, name
-    changeLogStr = @_createChangeLog name
-    manifest     = JSON.parse manifestStr
-    appPath      = @getAppPath manifest
-    # log manifestStr
-
-    stack = []
-
-    manifestFile  = FSHelper.createFileFromPath "#{appPath}/manifest.json"
-    changeLogFile = FSHelper.createFileFromPath "#{appPath}/ChangeLog"
-
-    # Copy default app files (app Skeleton)
-    stack.push (cb)=>
-      @vmController.run
-        method    : "app.skeleton"
-        withArgs  :
-          type    : if isBlank then "blank" else "sample"
-          appPath : appPath
-        , cb
-
-    stack.push (cb)=> manifestFile.save  manifestStr,  cb
-    stack.push (cb)=> changeLogFile.save changeLogStr, cb
-
-    async.series stack, (err, result) =>
-      warn err  if err
-      callback? err, result
-
-  # #
-  # FORK / CLONE APP
-  # #
-
-  downloadAppSource:(path, callback)->
-
-    @fetchApps =>
-      manifest = getManifestFromPath path
-
-      unless manifest
-        callback new KDNotificationView type : "mini", title : "Please refresh your apps and try again!"
-        return
-
-      @vmController.run
-        method       : "app.download"
-        withArgs     :
-          owner      : manifest.authorNick
-          identifier : manifest.identifier
-          appPath    : @getAppPath manifest
-          version    : manifest.version
-      , (err, res)=>
-        if err
-          warn err
-          callback? err
-        else
-          callback? null
-
-  # #
-  # HELPERS
-  # #
-
-  putStyleSheets = (manifest)->
-    {name, devMode, version, identifier} = manifest
-    {stylesheets} = manifest.source if manifest.source
-
-    return unless stylesheets
-
-    $("head .app-#{__utils.slugify name}").remove()
-    stylesheets.forEach (sheet)->
-      if devMode
-        urlToStyle = "https://#{KD.nick()}.#{KD.config.userSitesDomain}/.applications/#{__utils.slugify name}/#{__utils.stripTags sheet}?#{Date.now()}"
-        $('head').append "<link class='app-#{__utils.slugify name}' rel='stylesheet' href='#{urlToStyle}'>"
-      else
-        if /(http)|(:\/\/)/.test sheet
-          warn "external sheets cannot be used"
-        else
-          sheet = sheet.replace /(^\.\/)|(^\/+)/, ""
-          $('head').append("<link class='app-#{__utils.slugify name}' rel='stylesheet' href='#{KD.appsUri}/#{manifest.authorNick or KD.nick()}/#{__utils.stripTags identifier}/#{__utils.stripTags version}/#{__utils.stripTags sheet}'>")
-
-  showError = (error)->
-    new KDModalView
-      title   : "An error occured while running the App!"
-      width   : 500
-      overlay : yes
-      content : """
-

-
#{error.constructor.name}

-
#{error.message}

-

-

-                  Check Console for more details.
-

-                """
-                # We may after put a full stck to the output
-                # It looks weird for now.
-                #
#{error.stack}

-
-    console.warn error.message, error
-
-  showUpdateRequiredModal: (manifest) ->
-    {name} = manifest
-    modal  = new KDModalView
-      title          : "Update Required for #{name}"
-      content        : """
-

-
Author of #{name} made this update required. You must update to keep on using the app.

-
Warning: Updating an app will delete it's current folder to install new version. This cannot be undone. If you have updated files, back up them now.

-

-      """
-      overlay        : yes
-      buttons        :
-        Update       :
-          style      : "modal-clean-green"
-          loader     :
-            color    : "#FFFFFF"
-            diameter : 12
-          callback   : =>
-            @updateUserApp manifest, ->
-              modal.buttons.Update.hideLoader()
-              modal.destroy()
-        "Close" :
-          style      : "modal-cancel"
-          callback   : => modal.destroy()
-
-  createExtensionToAppMap: ->
-    @extensionToApp = map = {}
-
-    for own key, app of @getManifests()
-      fileTypes = app.fileTypes
-      continue  unless fileTypes
-      for type in fileTypes
-        map[type] or= []
-        map[type].push app.name
-
-    # Still there should be a more elagant way to add ace file types into map.
-    for type in KD.getAppOptions("Ace").fileTypes
-      map[type] or= []
-      map[type].push "Ace"
-
-  fetchUserDefaultAppConfig: ->
-    @appConfigStorage = new AppStorage "DefaultAppConfig", "1.0"
-    @appConfigStorage.fetchStorage (storage) =>
-      settings = @appConfigStorage.getValue "settings"
-      for own extension, appName of settings
-        @appManager.defaultApps[extension] = appName
-
-  updateDefaultAppConfig: (extension, appName) ->
-    {defaultApps} = @appManager
-    defaultApps[extension] = appName
-    @appConfigStorage.setValue "settings", defaultApps
-

   defaultManifest = (type, name)->
     {profile} = KD.whoami()
@@ -978,6 +158,826 @@

     json = JSON.stringify raw, null, 2

+  newAppModal = null
+
+  getAppPath:(manifest, escaped=no)->
+
+    path = if 'string' is typeof manifest then manifest else manifest.path
+    path = if /^~/.test path then "/home/#{KD.nick()}#{path.substr(1)}"\
+           else path
+    return FSHelper.escapeFilePath path  if escaped
+    return path.replace /(\/+)$/, ""
+
+  makeNewApp:(callback)->
+
+    return callback? yes if newAppModal
+
+    newAppModal = new KDModalViewWithForms
+      title                       : "Create a new Application"
+      content                     :
+        """

+            Please select the application type you want to start with.
+            Alternatively, you can modify an existing app; all applications are installed under ~/Applications folder,
+            you can right click on any of them, go to Applications menu, and click Download source files and start
+            reading manifest.json
+

+        """
+      overlay                     : yes
+      width                       : 400
+      height                      : "auto"
+      tabs                        :
+        navigable                 : yes
+        forms                     :
+          form                    :
+            buttons               :
+              Create              :
+                cssClass          : "modal-clean-gray"
+                loader            :
+                  color           : "#444444"
+                  diameter        : 12
+                callback          : =>
+                  unless newAppModal.modalTabs.forms.form.inputs.name.validate()
+                    newAppModal.modalTabs.forms.form.buttons.Create.hideLoader()
+                    return
+                  name        = newAppModal.modalTabs.forms.form.inputs.name.getValue()
+                  type        = newAppModal.modalTabs.forms.form.inputs.type.getValue()
+                  name        = name.replace(/[^a-zA-Z0-9\/\-.]/g, '')  if name
+                  manifestStr = defaultManifest type, name
+                  manifest    = JSON.parse manifestStr
+                  appPath     = @getAppPath manifest
+
+                  # FIXME Use default VM ~ GG
+                  FSHelper.exists appPath, null, (err, exists)=>
+                    if exists
+                      newAppModal.modalTabs.forms.form.buttons.Create.hideLoader()
+                      new KDNotificationView
+                        type      : "mini"
+                        cssClass  : "error"
+                        title     : "App folder with that name is already exists, please choose a new name."
+                        duration  : 3000
+                    else
+                      @prepareApplication {isBlank : type is "blank", name}, (err, response)=>
+                        callback? err
+                        newAppModal.modalTabs.forms.form.buttons.Create.hideLoader()
+                        newAppModal.destroy()
+
+            fields                :
+              type                :
+                label             : "Type"
+                itemClass         : KDSelectBox
+                type              : "select"
+                name              : "type"
+                defaultValue      : "sample"
+                selectOptions     : [
+                  { title : "Sample Application", value : "sample" }
+                  { title : "Blank Application",  value : "blank"  }
+                ]
+              name                :
+                label             : "Name:"
+                name              : "name"
+                placeholder       : "name your application..."
+                validate          :
+                  rules           :
+                    regExp        : /^[a-z\d]+([-][a-z\d]+)*$/i
+                  messages        :
+                    regExp        : "For Application name only lowercase letters and numbers are allowed!"
+
+    newAppModal.once "KDObjectWillBeDestroyed", ->
+      newAppModal = null
+      callback? yes
+
+  _createChangeLog:(name)->
+    today = new Date().format('yyyy-mm-dd')
+    {profile} = KD.whoami()
+    fullName  = KD.utils.getFullnameFromAccount()
+
+    """
+     #{today} #{fullName} <@#{profile.nickname}>
+
+        * #{name} (index.coffee): Application created.
+    """
+
+  prepareApplication:({isBlank, name}, callback)->
+
+    type         = if isBlank then "blank" else "sample"
+    name         = if name is "" then null else name
+    name         = name.replace(/[^a-zA-Z0-9\/\-.]/g, '')  if name
+    manifestStr  = defaultManifest type, name
+    changeLogStr = @_createChangeLog name
+    manifest     = JSON.parse manifestStr
+    appPath      = @getAppPath manifest
+    # log manifestStr
+    stack = []
+
+    manifestFile  = FSHelper.createFileFromPath "#{appPath}/manifest.json"
+    changeLogFile = FSHelper.createFileFromPath "#{appPath}/ChangeLog"
+
+    {vmController} = KD.singletons
+
+    # Copy default app files (app Skeleton)
+    stack.push (cb)=>
+      vmController.run
+        method    : "app.skeleton"
+        withArgs  :
+          type    : if isBlank then "blank" else "sample"
+          appPath : appPath
+        , cb
+
+    stack.push (cb)=> manifestFile.save  manifestStr,  cb
+    stack.push (cb)=> changeLogFile.save changeLogStr, cb
+
+    async.series stack, (err, result) =>
+      warn err  if err
+      callback? err, result
+
+  ###
+
+  @manifests = {}
+
+  @getManifestFromPath = getManifestFromPath = (path)->
+    folderName = (p for p in path.split("/") when /\.kdapp/.test p)[0]
+    app        = null
+    return app unless folderName
+    for own name, manifest of KodingAppsController.manifests
+      do -> app = manifest  if manifest.path.search(folderName) > -1
+    return app
+
+  constructor:->
+
+    super
+
+    @_loadedOnce    = no
+    @appManager     = KD.getSingleton "appManager"
+    @vmController   = KD.getSingleton "vmController"
+    mainController  = KD.getSingleton "mainController"
+    @manifests      = KodingAppsController.manifests
+    @publishedApps  = {}
+    @_fetchQueue    = []
+    @appStorage     = KD.getSingleton('appStorageController').storage 'Finder', '1.1.1'
+    @watcher        = new AppsWatcher
+
+    # @fetchApps =>
+    #   @getPublishedApps()
+    #   @createExtensionToAppMap()
+    #   @fetchUserDefaultAppConfig()
+
+    @on "UpdateDefaultAppConfig", (extension, appName) =>
+      @updateDefaultAppConfig extension, appName
+
+    mainController.on "accountChanged.to.loggedIn", @bound "getPublishedApps"
+
+    #  - NewAppIsAdded
+    #  - FileIsRemoved
+    #  - AppIsRemoved
+    #  - FileHasChanged
+    #  - ManifestHasChanged
+
+    @watcher.on "NewAppIsAdded", (app, change)=>
+      manifestPath = "#{change.file.fullPath}/manifest.json"
+      manifest = FSHelper.createFileFromPath manifestPath
+      manifest.exists (err, exists)=>
+        return  unless exists
+        @fetchAppFromFs app, =>
+          @putAppsToAppStorage null, =>
+            @emit "UpdateAppData", app
+
+    @watcher.on "AppIsRemoved", (app, change)=>
+      @invalidateDeletedApps [app], no, =>
+        @emit "InvalidateApp", app
+
+    @watcher.on "ManifestHasChanged", (app, change)=>
+      @fetchAppFromFs app, =>
+        @putAppsToAppStorage null, =>
+          @emit "UpdateAppData", app
+
+  # #
+  # FETCHERS
+  # #
+
+  fetchApps:(callback)->
+
+    kb = (manifests)=>
+      callback null, manifests
+      @watcher._trackedApps = if manifests then Object.keys manifests else []
+
+    @appStorage.ready =>
+      manifests = @getManifests()
+      apps      = Object.keys manifests
+      if apps.length > 0 then kb manifests
+      else
+        @fetchAppsFromDb (err, apps)=>
+          if err
+            @fetchAppsFromFs (err, apps)=>
+              return callback? err  if err
+              kb apps
+          else if err
+            callback? err
+          else
+            kb apps
+
+  fetchAppFromFs:(appName, cb)->
+
+    manifests = @getManifests()
+    appsPath = "/home/#{KD.nick()}/Applications/"
+    suffix   = ".kdapp/manifest.json"
+
+    manifest = FSHelper.createFileFromPath "#{appsPath}#{appName}#{suffix}"
+    manifest.fetchContents (err, response)=>
+      # warn err  if err
+      return cb null  if err or not response
+
+      try
+        manifest = JSON.parse response
+        manifests[manifest.name] = manifest
+      catch e
+        warn "Manifest file is broken:", e
+        return cb null
+
+      @constructor.manifests = manifests
+      cb null
+
+  fetchAppsFromFsHelper:(apps, callback)->
+
+    stack = []
+    apps.forEach (app)=>
+      stack.push (cb)=> @fetchAppFromFs app, cb
+    async.parallel stack, callback
+
+  fetchAppsFromFs:(cb)->
+
+    @_fetchQueue.push cb  if cb
+    return if @_fetchQueue.length > 1
+
+    @watcher.watch KD.utils.getTimedOutCallback (err, files)=>
+      @_loadedOnce = yes
+      if err or not Array.isArray files or files.length is 0
+        @putAppsToAppStorage {}
+        callback()  for callback in @_fetchQueue
+        @_fetchQueue = []
+      else
+        apps = @filterAppsFromFileList files
+        @fetchAppsFromFsHelper apps, (result)=>
+          @putAppsToAppStorage()
+          for callback in @_fetchQueue
+            callback null, @getManifests()
+          @_fetchQueue = []
+    , =>
+      warn msg = "Timeout reached for kite request"
+      KD.logToExternalWithTime msg
+      callback() for callback in @_fetchQueue
+      @_fetchQueue = []
+    , KD.config.fileFetchTimeout
+
+  fetchAppsFromDb:(callback)->
+
+    @appStorage.fetchValue 'apps', (apps)=>
+      @putDefaultShortcutsBack =>
+        if apps and Object.keys(apps).length > 0
+          @constructor.manifests = apps
+          callback null, apps
+        else
+          callback new Error "There are no apps in the app storage."
+
+  syncAppStorageWithFS:(force=no, callback=noop)->
+
+    currentApps = Object.keys(@getManifests())
+
+    # log "Synchronizing AppStorage with FileSystem..."
+
+    @watcher.watch (err, files)=>
+
+      return warn err  if err
+
+      existingApps = @filterAppsFromFileList files
+      newApps      = (app for app in existingApps when app not in currentApps) or []
+      removedApps  = (app for app in currentApps  when app not in existingApps) or []
+
+      # log "APPS FOUND IN AppStorage:", currentApps
+      # log "APPS FOUND IN FS:", existingApps
+      # log "REMOVED APPS:", removedApps
+      # log "NEW APPS:", newApps
+
+      # log "Nothing changed"  if removedApps.length is 0 and \
+      #                           newApps.length is 0
+
+      @invalidateDeletedApps removedApps, force, =>
+        # log "DELETED APPS REMOVED FROM APPSTORAGE"
+        appsToFetch = if force then existingApps else newApps
+        # log "FOLLOWING APPS WILL BE FETCHED", appsToFetch
+        @fetchAppsFromFsHelper appsToFetch, =>
+          # log "APPS FETCHED"
+          @emit "AppsDataChanged", {removedApps, newApps, existingApps, force}
+
+      @_loadedOnce = yes
+      callback?()
+
+  filterAppsFromFileList:(files)->
+    return (file.name.replace /\.kdapp$/, '' \
+            for file in files when (/\.kdapp$/.test file.name) \
+            and file.type is 'folder')
+
+  fetchUpdateAvailableApps: (callback, force) ->
+    return callback? null, @updateAvailableApps  if @updateAvailableApps and not force
+    {publishedApps}      = @
+    @updateAvailableApps = []
+
+    @fetchApps (err, apps) =>
+      for own appName, app of apps
+        if @isAppUpdateAvailable app.name, app.version
+          @updateAvailableApps.push publishedApps[app.name]
+      callback? null, @updateAvailableApps
+
+  fetchCompiledAppSource:(manifest, callback)->
+
+    indexJs = FSHelper.createFileFromPath "#{@getAppPath manifest}/index.js"
+    indexJs.fetchContents callback
+
+  # #
+  # MISC
+  # #
+
+  removeShortcut:(shortcut, callback)->
+    @appStorage.fetchValue 'shortcuts', (shortcuts)=>
+      delete shortcuts[shortcut]
+      @appStorage.setValue 'shortcuts', shortcuts, (err)->
+        callback err
+
+  putDefaultShortcutsBack:(callback)->
+    # if @appStorage.getValue 'shortcuts'
+    #   return  @utils.defer -> callback null
+
+    # @appStorage.reset()
+    @appStorage.setValue 'shortcuts', defaultShortcuts, (err)->
+      callback? err
+
+  putAppsToAppStorage:(apps, callback)->
+    # warn "calling putAppsToAppStorage:", apps
+    apps or= @getManifests()
+    @constructor.manifests = apps
+    @appStorage.setValue 'apps', apps, (err)-> callback? err
+
+  invalidateDeletedApps:(deletedApps, force=no, callback)->
+    # log "REQUESTED TO INVALIDATE:", deletedApps
+    return if force
+      @constructor.manifests = {}
+      @putAppsToAppStorage manifests, callback
+
+    manifests = @getManifests()
+    delete manifests[app]  for app in deletedApps
+    if deletedApps.length > 0
+      @putAppsToAppStorage manifests, callback
+    else
+      callback null
+
+  defineApp:(name, script)->
+    KD.registerAppScript name, script if script
+
+  getAppScript:(manifest, callback = noop)->
+
+    {name} = manifest
+
+    if script = KD.getAppScript name
+      callback null, script
+    else
+      @fetchCompiledAppSource manifest, (err, script)=>
+        if err
+          @compileApp name, callback
+        else
+          @defineApp name, script
+          callback err, script
+
+  getPublishedApps: (callback) ->
+    # return unless KD.isLoggedIn()
+    appNames = (appName for own appName, manifest of @getManifests()) or []
+    query    = "manifest.name": "$in": appNames
+    {JNewApp}   = KD.remote.api
+    JNewApp.fetchAllAppsData query, (err, apps)=>
+      @publishedApps = map = {}
+      apps?.forEach (app) =>
+        map[app.manifest.name] = new JNewApp app
+      @emit "UserAppModelsFetched", map
+      callback? map
+
+  isAppUpdateAvailable: (appName, appVersion) ->
+    if @publishedApps[appName]
+      return @utils.versionCompare appVersion, "lt", @publishedApps[appName].manifest.version
+
+  getAppUpdateType: (appName) ->
+    app = @publishedApps[appName]
+    return null unless app
+    return if app.manifest.forceUpdate is yes then "required" else "available"
+
+  updateAllApps:->
+    @fetchUpdateAvailableApps (err, apps) =>
+      return warn err  if err
+      stack = []
+      delete @notification
+      apps?.forEach (app) =>
+        stack.push (callback) =>
+          @updateUserApp app.manifest, callback
+      async.series stack
+
+  # added this to keep backward compatibility
+  refreshApps: (callback) -> @syncAppStorageWithFS yes, callback
+
+  updateUserApp:(manifest, callback)->
+    appName = manifest.name
+    unless @notification
+      @notification = new KDNotificationView
+        type        : "mini"
+        title       : "Updating #{appName}: Deleting old app files"
+        duration    : 10000
+
+    folder = FSHelper.createFileFromPath manifest.path, "folder"
+    folder.remove (err, res) =>
+      if err
+        @notification.setClass "error"
+        @notification.notificationSetTitle "An error occured while updating #{appName}."
+        return no
+      @refreshApps =>
+        @notification.notificationSetTitle "Updating #{appName}: Fetching new app details"
+        KD.remote.api.JNewApp.someWithRelationship { "manifest.name": appName }, {}, (err, app) =>
+          @notification.notificationSetTitle "Updating #{appName}: Updating app to latest version"
+          @installApp app[0], app[0].versions.last, =>
+            @refreshApps()
+            callback?()
+            @emit "AnAppHasBeenUpdated"
+            @notification.notificationSetTitle "#{appName} has been updated successfully"
+      , yes
+
+  # #
+  # KITE INTERACTIONS
+  # #
+
+  hasForceUpdate: (appInstance) ->
+    manifest                 = @constructor.manifests[appInstance.getOptions().name]
+    {devMode, name, version} = manifest
+    forceUpdate              = @getAppUpdateType(name) is "required"
+    updateAvailable          = @isAppUpdateAvailable(name, version)
+    hasUpdate                = updateAvailable and not devMode and forceUpdate
+
+    @showUpdateRequiredModal manifest  if hasUpdate
+    return hasUpdate
+
+  putAppResources: (appInstance) ->
+    return  unless appInstance
+
+    manifest = appInstance.getOptions()
+    return  unless manifest.thirdParty
+
+    appView = appInstance.getView()
+    appView.addSubView loader = new KDLoaderView
+      loaderOptions :
+        color       : "#ff9200"
+        speed       : 2
+        range       : 0.7
+        density     : 60
+      cssClass      : "app-loading"
+      size          :
+        width       : 128
+
+    appView.once "viewAppended", -> loader.show()
+
+    putStyleSheets manifest
+
+    @getAppScript manifest, (err, appScript)=>
+      return warn err  if err
+
+      loader.destroy()
+      id = appView.getId()
+
+      try
+        # security please!
+        do (appView)->
+          eval "var appView = KD.instances[\"#{id}\"];\n\n" + appScript
+      catch error
+        # if not manifest.ignoreWarnings? # GG FIXME
+        showError error
+
+
+  publishApp:(path, callback)->
+
+    if not (KD.checkFlag('app-publisher') or KD.checkFlag('super-admin'))
+      err = "You are not authorized to publish apps."
+      log err
+      callback? err
+      return no
+
+    manifest = getManifestFromPath(path)
+    appName  = manifest.name
+
+    notification = new KDNotificationView
+      overlay       :
+        transparent : no
+        destroyOnClick: no
+      loader        :
+        color       : "#ffffff"
+      title         : "Please wait while we are publishing your app..."
+      followUps     :
+        duration    : 10000
+        title       : "We are still working on it. Your app will be published soon..."
+      duration      : 120000
+
+    @getAppScript manifest, (appScript)=>
+
+      manifest   = @getManifest appName
+      appPath    = @getAppPath manifest
+      options    =
+        method   : "app.publish"
+        withArgs : {appPath}
+
+      @vmController.run options, (err, res)=>
+        if err
+          warn err
+          notification.destroy()
+          callback? err
+        else
+          manifest.authorNick = KD.whoami().profile.nickname
+          jAppData     =
+            title      : manifest.name        or "Application Title"
+            body       : manifest.description or "Application description"
+            identifier : manifest.identifier  or "com.koding.apps.#{__utils.slugify manifest.name}"
+            manifest   : manifest
+
+          notification.destroy()
+
+          @createApp jAppData, (err, app) =>
+            if err
+              warn err
+              return callback? err
+            @appManager.open "Apps"
+            @appManager.tell "Apps", "updateApps"
+            callback?()
+
+  createApp:(formData, callback)->
+    KD.remote.api.JNewApp.create formData, (err, app)->
+      callback? err, app
+
+  compileApp:(name, callback)->
+
+    compileOnServer = (app)=>
+      return warn "#{name}: No such application!" unless app
+      appPath = @getAppPath app
+
+      loader = new KDNotificationView
+        duration : 18000
+        title    : "Compiling #{name}..."
+        type     : "mini"
+
+      @vmController.run "kdc #{appPath}", (err, response)=>
+        if not err
+          nickname    = KD.nick()
+          publishPath = "/home/#{nickname}/Web/.applications"
+          @vmController.run
+            method      : "fs.createDirectory"
+            withArgs    :
+              path      : publishPath
+              recursive : yes
+          , (err, response)=>
+            loader.notificationSetTitle "Publishing app static files..."
+            linkFile = "#{publishPath}/#{KD.utils.slugify name}"
+            @vmController.run "rm #{linkFile}; ln -s #{appPath} #{linkFile}", (err, response)=>
+              loader.notificationSetTitle "Fetching compiled app..."
+              @fetchCompiledAppSource app, (err, res)=>
+                if not err
+                  @defineApp name, res
+                  loader.notificationSetTitle "App compiled successfully"
+                  loader.notificationSetTimer 2000
+                callback? err, res
+        else
+          loader.destroy()
+
+          if err.message is "exit status 127"
+            modal = new ModalViewWithTerminal
+              title   : "Koding app compiler is not installed in your VM."
+              width   : 500
+              overlay : yes
+              terminal:
+                hidden: yes
+              content : """
+

+

+                            If you want to install it now, click Install Compiler button.
+

+

+                            Remember to enter your password when asked.
+

+

+                        """
+              buttons:
+                "Install Compiler":
+                  cssClass: "modal-clean-green"
+                  callback: =>
+                    modal.run "sudo npm install -g kdc; echo $?|kdevent;" # find a clean/better way to do it.
+
+            modal.on "terminal.event", (data)->
+              if data is "0"
+                new KDNotificationView title: "Installed successfully!"
+                modal.destroy()
+              else
+                new KDNotificationView
+                  title   : "An error occured."
+                  content : "Something went wrong while installing Koding App Compiler. Please try again."
+
+            callback? err
+            return
+
+          if response
+            details = """
#{response}
"""
+          else
+            details = ""
+
+          new KDModalView
+            title   : "An error occured while compiling the App!"
+            width   : 500
+            overlay : yes
+            content : """
+

+
#{err.message}

+                        #{details}
+

+                      """
+          callback? err
+
+    unless @getManifest name
+      @fetchApps (err, apps)->
+        compileOnServer apps[name]
+    else
+      compileOnServer @getManifest name
+
+  getManifests:->
+    @constructor.manifests
+
+  getManifest:(appName)->
+    @constructor.manifests[appName]
+
+  installApp:(app, version, callback)->
+
+    # add group membership control when group based apps feature is implemented!
+    KD.requireMembership
+      onFailMsg : "Login required to install Apps"
+      onFail    : => callback yes
+      callback  : => @fetchApps (err, manifests = {})=>
+
+        KD.showError err,
+          KodingError : 'Something went wrong while fetching apps'
+        return callback? err  if err
+
+        if app.title in Object.keys(manifests)
+          new KDNotificationView
+            type : "mini", title : "App is already installed!"
+          callback? yes
+        else
+          if app.approved isnt yes and not KD.checkFlag 'app-publisher'
+            KD.showError "This app is not approved, installation cancelled."
+            callback? err
+          else
+            app.fetchCreator (err, acc)=>
+              KD.showError err,
+                KodingError : 'Failed to fetch app creator info'
+              return callback? err  if err
+
+              KD.mixpanel "Install Application, success", app.manifest.identifier
+
+              @vmController.run
+                method       : "app.install"
+                withArgs     :
+                  owner      : acc.profile.nickname
+                  identifier : app.manifest.identifier
+                  appPath    : @getAppPath app.manifest
+                  version    : version
+              , (err, res)=>
+                if err
+                  KD.showError err,
+                    KodingError: """Something wrong with Apps server,
+                                    please try again later"""
+                  return callback?()
+                app.install (err)=>
+                  KD.showError err
+                  @appManager.open "StartTab"
+                  callback?()
+
+  # #
+  # FORK / CLONE APP
+  # #
+
+  downloadAppSource:(path, callback)->
+
+    @fetchApps =>
+      manifest = getManifestFromPath path
+
+      unless manifest
+        callback new KDNotificationView type : "mini", title : "Please refresh your apps and try again!"
+        return
+
+      @vmController.run
+        method       : "app.download"
+        withArgs     :
+          owner      : manifest.authorNick
+          identifier : manifest.identifier
+          appPath    : @getAppPath manifest
+          version    : manifest.version
+      , (err, res)=>
+        if err
+          warn err
+          callback? err
+        else
+          callback? null
+
+  # #
+  # HELPERS
+  # #
+
+  putStyleSheets = (manifest)->
+    {name, devMode, version, identifier} = manifest
+    {stylesheets} = manifest.source if manifest.source
+
+    return unless stylesheets
+
+    $("head .app-#{__utils.slugify name}").remove()
+    stylesheets.forEach (sheet)->
+      if devMode
+        urlToStyle = "https://#{KD.nick()}.#{KD.config.userSitesDomain}/.applications/#{__utils.slugify name}/#{__utils.stripTags sheet}?#{Date.now()}"
+        $('head').append "<link class='app-#{__utils.slugify name}' rel='stylesheet' href='#{urlToStyle}'>"
+      else
+        if /(http)|(:\/\/)/.test sheet
+          warn "external sheets cannot be used"
+        else
+          sheet = sheet.replace /(^\.\/)|(^\/+)/, ""
+          $('head').append("<link class='app-#{__utils.slugify name}' rel='stylesheet' href='#{KD.appsUri}/#{manifest.authorNick or KD.nick()}/#{__utils.stripTags identifier}/#{__utils.stripTags version}/#{__utils.stripTags sheet}'>")
+
+  showError = (error)->
+    new KDModalView
+      title   : "An error occured while running the App!"
+      width   : 500
+      overlay : yes
+      content : """
+

+
#{error.constructor.name}

+
#{error.message}

+

+

+                  Check Console for more details.
+

+                """
+                # We may after put a full stck to the output
+                # It looks weird for now.
+                #
#{error.stack}

+
+    console.warn error.message, error
+
+  showUpdateRequiredModal: (manifest) ->
+    {name} = manifest
+    modal  = new KDModalView
+      title          : "Update Required for #{name}"
+      content        : """
+

+
Author of #{name} made this update required. You must update to keep on using the app.

+
Warning: Updating an app will delete it's current folder to install new version. This cannot be undone. If you have updated files, back up them now.

+

+      """
+      overlay        : yes
+      buttons        :
+        Update       :
+          style      : "modal-clean-green"
+          loader     :
+            color    : "#FFFFFF"
+            diameter : 12
+          callback   : =>
+            @updateUserApp manifest, ->
+              modal.buttons.Update.hideLoader()
+              modal.destroy()
+        "Close" :
+          style      : "modal-cancel"
+          callback   : => modal.destroy()
+
+  createExtensionToAppMap: ->
+    @extensionToApp = map = {}
+
+    for own key, app of @getManifests()
+      fileTypes = app.fileTypes
+      continue  unless fileTypes
+      for type in fileTypes
+        map[type] or= []
+        map[type].push app.name
+
+    # Still there should be a more elagant way to add ace file types into map.
+    for type in KD.getAppOptions("Ace").fileTypes
+      map[type] or= []
+      map[type].push "Ace"
+
+  fetchUserDefaultAppConfig: ->
+    @appConfigStorage = new AppStorage "DefaultAppConfig", "1.0"
+    @appConfigStorage.fetchStorage (storage) =>
+      settings = @appConfigStorage.getValue "settings"
+      for own extension, appName of settings
+        @appManager.defaultApps[extension] = appName
+
+  updateDefaultAppConfig: (extension, appName) ->
+    {defaultApps} = @appManager
+    defaultApps[extension] = appName
+    @appConfigStorage.setValue "settings", defaultApps
+
   defaultShortcuts =
     Ace           :
       name        : 'Ace'


Apps: show only verified apps by default, hide the switch when my apps selected and show it again otherwise

--- client/Social/Apps/AppController.coffee
+++ client/Social/Apps/AppController.coffee
@@ -32,9 +32,10 @@

     super options, data

-    @_verifiedOnly = no
+    @_verifiedOnly = yes

   loadView:(mainView, firstRun = yes, loadFeed = no)->
+
     if firstRun

       @on "searchFilterChanged", (value) =>
@@ -49,7 +50,20 @@
     @createFeed mainView, loadFeed

   doQuery:(selector, options, callback)->
-    if @_verifiedOnly then selector['status'] = 'verified'
+
+    if selector['manifest.authorNick'] is KD.nick()
+
+      @_verifiedSwitch?.hide()
+      @_verifiedSwitchLabel?.hide()
+
+    else
+
+      @_verifiedSwitch?.show()
+      @_verifiedSwitchLabel?.show()
+
+      if @_verifiedOnly
+        selector['status'] = 'verified'
+
     KD.remote.api.JNewApp.some selector, options, callback

   createFeed:(view, loadFeed = no)->


some refactor

some refactor

--- client/app/MainApp/book/bookview.coffee
+++ client/app/MainApp/book/bookview.coffee
@@ -80,6 +80,8 @@

     @pagerWrapper.addSubView @pageNav

+    @on "PageFill", -> @checkBoundaries()
+
     @once "OverlayAdded", => @$overlay.css zIndex : 999

     @once "OverlayWillBeRemoved", =>
@@ -101,7 +103,6 @@

     @setKeyView()
     cachePage(0)
-    @checkBoundaries()
     KD.track "Read Tutorial Book", KD.nick()

   pistachio:->
@@ -149,9 +150,9 @@

   checkBoundaries: (pages=__bookPages)->
     if BookView.navigateNewPages
-      @getNewPages (pages)=>
+      @getNewPages (newPages)=>
         @toggleButton "prev", @newPagePointer is 0
-        @toggleButton "next", @newPagePointer is pages.length - 1
+        @toggleButton "next", @newPagePointer is newPages.length - 1
     else
       @toggleButton "prev", @currentIndex is 0
       @toggleButton "next", @currentIndex is pages.length - 1
@@ -166,7 +167,6 @@

     return if @currentIndex - 1 < 0
     @fillPage @currentIndex - 1
-    @checkBoundaries()

   fillNextPage:->
     if BookView.navigateNewPages
@@ -178,7 +178,6 @@

     return if __bookPages.length is @currentIndex + 1
     @fillPage parseInt(@currentIndex,10) + 1
-    @checkBoundaries()

   fillPage:(index)->
     cachePage index+1
@@ -198,6 +197,8 @@
       @right.unsetClass "out"
       @utils.wait 400, =>
         @setClass "in"
+
+    @emit "PageFill", index

     # destroy @pointer
     if @pointer then @destroyPointer()


new users seen newest version always

new users seen newest version always

--- client/app/MainApp/book/bookview.coffee
+++ client/app/MainApp/book/bookview.coffee
@@ -88,7 +88,8 @@
       if BookView.navigateNewPages
         BookView.navigateNewPages = no
         BookView.lastIndex = 0
-        @getStorage().setValue "lastReadVersion", @getVersion()
+
+      @getStorage().setValue "lastReadVersion", @getVersion()

     @once "OverlayWillBeRemoved", =>
       if @pointer then @destroyPointer()


Fix problem of case of Flux store file

Fix problem of case of Flux store file

--- client/app/libs/flux/store/Store.coffee
+++ client/app/libs/flux/store/Store.coffee
@@ -1,39 +0,0 @@
-AppDispatcher = require '../../../app_dispatcher'
-
-
-module.exports = class Store extends EventEmitter
-
-    uniqID: null
-
-    # this variable will be shared with all subclasses so we store the items
-    # by subclass we don't use `@constructor.name` because it breaks when
-    # mangled
-    _nextUniqID = 0
-    _handlers = {}
-    _addHandlers = (type, callback) ->
-
-        _handlers[@uniqID] = {} unless _handlers[@uniqID]?
-        _handlers[@uniqID][type] = callback
-
-    # Registers the store's callbacks to the dispatcher
-    _processBinding = ->
-        @dispatchToken = AppDispatcher.register (payload) =>
-            {type, value} = payload.action
-            if (callback = _handlers[@uniqID][type])?
-                callback.call @, value
-
-
-    constructor: ->
-        super()
-        # set a uniq ID
-        @uniqID = _nextUniqID++
-        @__bindHandlers _addHandlers.bind @
-        _processBinding.call @
-
-    # Must be overriden by each store
-    __bindHandlers: (handle) ->
-        if __DEV__
-            message = "The store #{@constructor.name} must define a " + \
-                      "`__bindHandlers` method"
-            throw new Error message
-


socket.io leaves rooms before disconnect is emitted now

--- packages/shrub-socket.io/index.coffee
+++ packages/shrub-socket.io/index.coffee
@@ -75,15 +75,18 @@
 		# Connection (post-authorization).
 		@io.on 'connection', (socket) =>

+			# Run the disconnection middleware on socket close.
+			oncloseProxy = socket.onclose
+			socket.onclose = =>
+				@_disconnectionMiddleware.dispatch socket.request, null, (error) ->
+					return logger.error errors.stack error if error?
+
+				oncloseProxy.call socket
+
 			# Join a '$global' channel.
 			socket.join '$global', (error) =>
 				return logger.error errors.stack error if error?

-				# Run the disconnection middleware on disconnect.
-				socket.on 'disconnect', =>
-					@_disconnectionMiddleware.dispatch socket.request, null, (error) ->
-						return logger.error errors.stack error if error?
-
 				# Dispatch the connection middleware.
 				@_connectionMiddleware.dispatch socket.request, null, (error) =>
 					return logger.error errors.stack error if error?


Add colon for ports

Add colon for ports

--- client/views/instances/instances.coffee
+++ client/views/instances/instances.coffee
@@ -29,7 +29,7 @@
   stopButtonText: -> if @meta.state isnt 'active' then 'Destroy' else 'Stop'
   instanceLink: ->
     port = findWebPort @services?.www
-    endpoint = @services?.www?.endpoint or port
+    endpoint = @services?.www?.endpoint or ":" + port
     protocol = determineProtocol port
     if @services?.www?.hostname
       "#{protocol}://#{@services?.www?.hostname}#{endpoint}"


don't cache if fusion

don't cache if fusion

--- apps/artwork/routes.coffee
+++ apps/artwork/routes.coffee
@@ -7,11 +7,12 @@
 { stringifyJSONForWeb } = require '../../components/util/json.coffee'
 { client } = require '../../lib/cache'
 request = require 'superagent'
+{ FUSION_URL } = require '../../config'

 @index = (req, res, next) ->
   artwork = new Artwork id: req.params.id
   artwork.fetch
-    cache: true
+    cache: not FUSION_URL?
     error: res.backboneError
     success: (model, response, options) ->
       # Remove the current artwork tab from the path to more easily test against artwork.href()


Properly 404

Properly 404

--- apps/auction/routes.coffee
+++ apps/auction/routes.coffee
@@ -24,7 +24,7 @@

   dfd.promise

-@index = (req, res) ->
+@index = (req, res, next) ->
   id = req.params.id

   auction = new Auction id: id
@@ -40,7 +40,7 @@
     auction.fetch(cache: true)
     saleArtworks.fetchUntilEndInParallel(cache: true)
     setupUser(req.user, auction)
-  ]).spread((a, b, user) ->
+  ]).spread (a, b, user) ->
     artworks.reset Artworks.__fromSale__(saleArtworks)

     res.locals.sd.AUCTION = auction.toJSON()
@@ -54,4 +54,7 @@
       user: user
       state: state

-  ).done()
+  , ->
+    err = new Error 'Not Found'
+    err.status = 404
+    next err


Clear in progress text.

Clear in progress text.

--- app/assets/javascripts/chains.coffee
+++ app/assets/javascripts/chains.coffee
@@ -66,6 +66,7 @@
     confirm_node_created: () ->
       $.get "confirm_node_created?title=#{$('#blockchain_title').data('title')}", (rsp) =>
         if rsp.message is 'node created'
+          @lockchain.clear_output 'in_progress'
           @blockchain.update_output $("
Node has been created.
"), 'complete'
           @blockchain.update_output $("
IPv4 Address: #{rsp.ipv4_address}.
"), 'complete'
           @blockchain.update_output $("
IPv6 Address: #{rsp.ipv6_address}.
"), 'complete'


Make multiple loads of strut pick up latest ppt data properly.

--- app/assets/javascripts/ppt.js.coffee
+++ app/assets/javascripts/ppt.js.coffee
@@ -18,9 +18,10 @@
                #]
       #}
     #]
-  localStorage['Strut_sessionMeta'] = '{"generator_index":0,"lastPresentation":"presentation-unnamed.strut"}'
-  localStorage['strut-presentation-unnamed.strut'] = JSON.stringify(pptData)
-  $('#editor').attr('src', '/editor/')
+  $('#editor').removeAttr('src').one 'load', ->
+    localStorage['Strut_sessionMeta'] = '{"generator_index":0,"lastPresentation":"presentation-unnamed.strut"}'
+    localStorage['strut-presentation-unnamed.strut'] = JSON.stringify(pptData)
+    $('#editor').attr('src', '/editor/')

 $ ->
   $('#ppt-loader').on 'click', loadPpt


Fix minor spelling bug in url

Fix minor spelling bug in url

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -246,7 +246,7 @@

   uppy

-imageUpload = (fileInput,endpoint='/screenshot/upload', classname="course") ->
+imageUpload = (fileInput,endpoint='/screenshots/upload', classname="course") ->
   # set everything up
   uploadArea = document.getElementById('image-uploadArea')
   uploadButton = document.getElementById('image-uploadButton')
@@ -703,10 +703,10 @@
   videoUpload video if video?
   manuscriptUpload manuscript if manuscript?
   geogebraUpload geogebra if geogebra?
-  uploadPath = "/screenshot/upload"
+  uploadPath = "/screenshots/upload"
   uploadPath = "/profile_image/upload" if image2?
   imageUpload image2, uploadPath, "user" if image2?
-  imageUpload image, uploadPath if image?
+  imageUpload image, uploadPath, "course" if image?
   bulkCorrectionUpload bulkCorrection if bulkCorrection?

   # make uppy upload buttons look like bootstrap


** fixing git hell

** fixing git hell

--- app/assets/javascripts/webrtc.coffee
+++ app/assets/javascripts/webrtc.coffee
@@ -59,12 +59,35 @@
     @sdpConstraints = {'mandatory': {'OfferToReceiveAudio':true, 'OfferToReceiveVideo':true }}
     @pc_config = {"iceServers":
           [
-              #{url:'stun:stun.l.google.com:19302'},
-              {url:'stun:waturn.cloudapp.net'},
+              {url:'stun:stun.l.google.com:19302'},
+              {url:'stun:stun01.sipphone.com'},
+              {url:'stun:stun.ekiga.net'},
+              {url:'stun:stun.fwdnet.net'},
+              {url:'stun:stun.ideasip.com'},
+              {url:'stun:stun.iptel.org'},
+              {url:'stun:stun.rixtelecom.se'},
+              {url:'stun:stun.schlund.de'},
+              {url:'stun:stun.l.google.com:19302'},
+              {url:'stun:stun1.l.google.com:19302'},
+              {url:'stun:stun2.l.google.com:19302'},
+              {url:'stun:stun3.l.google.com:19302'},
+              {url:'stun:stun4.l.google.com:19302'},
+              {url:'stun:stunserver.org'},
+              {url:'stun:stun.softjoys.com'},
+              {url:'stun:stun.voiparound.com'},
+              {url:'stun:stun.voipbuster.com'},
+              {url:'stun:stun.voipstunt.com'},
+              {url:'stun:stun.voxgratia.org'},
+              {url:'stun:stun.xten.com'},
               {
                   url: 'turn:waturn.cloudapp.net:443?transport=tcp',
                   credential: 'walkaboutpass',
                   username: 'walkabout'
+              },
+              {
+                  url: 'turn:numb.viagenie.ca',
+                  credential: 'muazkh',
+                  username: 'webrtc@live.com'
               },
               {
                   url: 'turn:192.158.29.39:3478?transport=udp',
@@ -191,12 +214,12 @@
     peer_connection = new RTCPeerConnection(@pc_config, {"optional": []} )
     @peers[peer_id] = peer_connection
     peer_connection.oniceconnectionstatechange = (ev) ->
+      console.log('oniceconnectionstatechange', ev)
       console.log('peer_connection.iceConnectionState == ' + peer_connection.iceConnectionState )
       if( peer_connection.iceConnectionState == 'completed' )
         console.log('WE HAVE A PEER CONNECTION....')
     peer_connection.onicecandidate = (event) =>
       if (event.candidate)
-        console.log('onicecandidate: ' + peer_connection.icegatheringstatus)
         @send('relay', {
                   'type':'iceCandidate'
                   'actors': [peer_id],
@@ -205,9 +228,6 @@
                     'candidate': event.candidate.candidate
                   }
         })
-    peer_connection.onsignalingstatechange = (ev) ->
-      console.log('Signaling state changed to: ' + peer_connection.signalingState)
-      console.log('icegatheringstatus: ' + peer_connection.icegatheringstatus)

     peer_connection.onaddstream = (event) =>
       console.log("onAddStream: ", event)
@@ -275,6 +295,7 @@
     merged.optional = merged.optional.concat(cons2.optional)
     merged

+
   # Peers exchange session descriptions which contains information
   # about their audio / video settings and that sort of stuff. First
   # the 'offerer' sends a description to the 'answerer' (with type
@@ -298,7 +319,6 @@

       desc = new RTCSessionDescription(remote_description)
       console.log("Description Object: ", desc)
-

       peer.setRemoteDescription(desc, =>
         console.log("setRemoteDescription succeeded")
@@ -330,7 +350,6 @@

-
   # The offerer will send a number of ICE Candidate blobs to the answerer so they
   # can begin trying to find the best path to one another on the net.
   iceCandidate: (config) ->
@@ -342,7 +361,7 @@
       , ->
         console.log('Added the ICE Candidate.')
       , (err) ->
-        console.log('[ERROR] - ICE Candidate. ' + err)
+        console.log('[ERROR] - ICE Candidate.' + err)
       )
     else
       #alert('[ERROR] - no peer for peer_id: '+config.peer_id)
@@ -371,15 +390,15 @@

   doGetUserMedia: (callback) ->
-    if( @localStream? )
+    if( @localStream != null)
       if( callback )
         callback()
-      return
-    constraints = {"audio": true, "video": {"mandatory": {}, "optional": []}}
+        return
+    constraints = {"audio": true, "video": {"mandatory": {}, "optional": []}};
     try
-      console.log("Requested access to local media with mediaConstraints:\n \"" + JSON.stringify(constraints) + "\"")
+      console.log("Requested access to local media with mediaConstraints:\n \"" + JSON.stringify(constraints) + "\"");
       getUserMedia(constraints, (stream) =>
-        @localStream = stream
+        @localStream = stream
         local_media = if @USE_VIDEO
                         $("<video>")
                       else


Be compatiable with IE 10

Be compatiable with IE 10

--- src/app/controllers/login.coffee
+++ src/app/controllers/login.coffee
@@ -5,7 +5,7 @@
     { name: 'Chrome', url: 'http://www.google.com/chrome', icon: '/images/chrome.png' }
     { name: 'Firefox', url: 'http://www.mozilla.org/en-US/firefox/new/', icon: '/images/firefox.png' }
     { name: 'Safari', url: 'http://www.apple.com/sg/safari/', icon: '/images/safari.png' }
-    { name: 'IE 9+', url: 'http://windows.microsoft.com/en-sg/internet-explorer/ie-10-worldwide-languages', icon: '/images/ie.png' }
+    { name: 'IE 10+', url: 'http://windows.microsoft.com/en-sg/internet-explorer/ie-10-worldwide-languages', icon: '/images/ie.png' }
   ]

   actions:


Alert push token

Alert push token

--- www/coffee/app.coffee
+++ www/coffee/app.coffee
@@ -54,7 +54,7 @@
         return
       'onRegister': (data) ->
         console.log 'Device token (X):', data.token
-        # alert data.token
+        alert data.token
         user = $ionicUser.get()
         if not user.user_id
           user.user_id = $ionicUser.generateGUID()


More editing improvements for ss record views

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -209,6 +209,11 @@
               Rows[@i]
             d: ->
               Data[@c].id[@n]?[@i]
+            p: ->
+              cls = 'ss-'
+              if typeof @c != 'number'
+                cls += @c.charAt(0)
+              cls
             slot: ->
               volume.containers[if @c == 'slot' then @d else Data.slot.id[0][@i]]
             record: ->
@@ -278,7 +283,7 @@
             c = record.category || 0

             # populate depends:
-            if record.id of Depends
+            if Depends[record.id]
               # skip duplicates:
               continue if i of Depends[record.id]
             else
@@ -322,17 +327,17 @@
             Counts[i][record.category || 0] = 1

             populateRecordData(i, 0, record)
-
-            Depends[r] = {}
-            Depends[r][i] = 1
-
             records[r] = i++

           for s, slot of volume.containers when Top != !slot.top
+            deps = Depends[slot.id] = {}
             for rr in slot.records
               if (j = records[rr.id])?
                 n = inc(Counts[j], 'slot')
                 populateSlotData(j, n, slot)
+                if !Editing && age of rr
+                  populateDatum(j, 'slot', n, 'age', rr.age)
+                deps[j] = 1
           ### jshint ignore:end ###

           i
@@ -443,7 +448,7 @@
               v = ''
             when 'id'
               if v?
-                cell.className = 'icon ' + if Editing then 'trash' else 'bullet'
+                cell.className = 'icon ' + if Editing && Key.id != info.c then 'trash' else 'bullet'
                 v = ''
             when 'age'
               v = display.formatAge(v)
@@ -506,9 +511,7 @@
           return unless l = ms.length
           t = info.count
           r = Data[info.c]
-          cls = 'ss-'
-          if typeof info.c != 'number'
-            cls += info.c.charAt(0)
+          cls = info.p
           if td = generateMultiple(info) # (col, l, row, i, n, t)
             unless info.hasOwnProperty('n')
               for n in [0..t-1] by 1
@@ -729,14 +732,29 @@
             setFocus = undefined
             record

+        updateDatum = (info, v) ->
+          info.v = v
+          rcm = Data[info.c][info.metric.id]
+          if info.c == Key.c || info.c == 'asset'
+            rcm[info.n][info.i] = v
+            generateText(info)
+          else
+            for i, n of Depends[info.d]
+              arr(rcm, n)[i] = v
+              # TODO age may have changed... not clear how to update.
+            l = TBody.getElementsByClassName(info.p + info.d + '_' + info.metric.id)
+            for li in l
+              info.cell = li
+              generateText(info)
+          return
+
         saveDatum = (info, v) ->
           if info.c == 'slot'
             data = {}
             data[info.metric.id] = v ? ''
             return if info.slot[info.metric.id] == v
             saveRun info.cell, info.slot.save(data).then () ->
-              info.v = Data[info.c][info.metric.id][info.n][info.i] = v
-              generateText(info) #, cell, info.t, info.slot[info.t])
+              updateDatum(info, v)
               return
           else if info.c == 'asset'
             data = {}
@@ -744,21 +762,12 @@
             data[t] = v ? ''
             return if info.asset[t] == data[t]
             saveRun info.cell, info.asset.save(data).then () ->
-              info.v = Data[info.c][info.metric.id][info.n][info.i] = v
-              generateText(info)
+              updateDatum(info, v)
               return
           else
             return if info.record.measures[info.metric.id] == v
             saveRun info.cell, info.record.measureSet(info.metric.id, v).then (rec) ->
-              rcm = Data[rec.category || 0][info.metric.id]
-              for i, n of Depends[info.d]
-                arr(rcm, n)[i] = v
-                # TODO age may have changed... not clear how to update.
-              info.v = v
-              l = TBody.getElementsByClassName('ss-' + info.d + '_' + info.metric.id)
-              for li in l
-                info.cell = li
-                generateText(info)
+              updateDatum(info, v)
               return

         ################################# Interaction
@@ -798,7 +807,7 @@
           info.row.classList.add('expand')

           max = expanded.count
-          max++ if Editing && expanded.category != Key
+          max++ if Editing && expanded.c != Key.id && expanded.c != 'slot'
           return if max <= 1
           next = info.row.nextSibling
           start = expanded.count == 1
@@ -904,7 +913,7 @@
               m = info.metric.id
               if m == 'id'
                 # trash/bullet: remove
-                setRecord(info, null)
+                setRecord(info, null) if info.c != Key.id
                 return
               return if info.metric.readonly
               editScope.type = info.metric.type
@@ -924,7 +933,7 @@
                   rs = []
                   mf = (r) -> (m) -> r.measures[m]
                   for ri, r of volume.records
-                    if (r.category || 0) == info.category.id && !(ri of Depends && info.i of Depends[ri])
+                    if (r.category || 0) == info.c && !Depends[ri]?[info.i]
                       rs.push
                         r:r
                         v:(r.measures[info.metric.id] ? '').toLowerCase()
@@ -943,16 +952,13 @@
                 return
               return if info.slot?.id == volume.top.id
               c = info.category
-              if info.d?
-                editInput.value = info.d + ''
-              else
-                editInput.value = 'remove'
+              editInput.value = (info.d ? 'remove')+''
               editScope.type = 'record'
               editScope.options =
                 new: 'Create new ' + c.name
                 remove: c.not
               for ri, r of volume.records
-                if (r.category || 0) == c.id && (!(ri of Depends && info.i of Depends[ri]) || ri == editInput.value)
+                if (r.category || 0) == c.id && (!Depends[ri]?[info.i] || ri == editInput.value)
                   editScope.options[ri] = r.displayName
               # detect special cases: singleton or unitary records
               for mi of Data[c.id]


new +New button: make it bring up new tab when no input given; also make it work better when extension is given.

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -1258,18 +1258,27 @@

         # the search/mini file creation box
-        mini_set_input = () =>
-            search_box = @container.find(".salvus-project-search-for-file-input")
-            name = search_box.val().trim()
+        mini_search_box = @container.find(".salvus-project-search-for-file-input")
+        mini_set_input = (name) =>
+            if not name?
+                name = mini_search_box.val().trim()
             if name == ""
                 name = @default_filename()
             @update_new_file_tab_path()
             @new_file_tab_input.val(name)
-            search_box.val('')
+            mini_search_box.val('')

         @container.find("a[href=#smc-mini-new]").click () =>
-            mini_set_input()
-            create_file('sagews')
+            name = mini_search_box.val().trim()
+            if name
+                mini_set_input()
+                ext = misc.filename_extension(name)
+                if ext
+                    create_file(ext)
+                else
+                    create_file('sagews')
+            else
+                @load_target('new')

         @container.find(".smc-mini-new-file-type-list").find("a[href=#new-file]").click (evt) ->
             mini_set_input()


prevent an event emitter leak for synchronized documents (hopefully)

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -199,6 +199,9 @@

     _add_listeners: () =>
         # We *have* to wrapper all the listeners
+        if @_listeners?
+            # if we already added listeners before (for a prior connection?), remove them before re-adding them?
+            @_remove_listeners()
         @_listeners =
             codemirror_diffsync_ready : ((mesg) => @__diffsync_ready(mesg))
             codemirror_bcast          : ((mesg) => @__receive_broadcast(mesg))


Activity: minor cleanup

Activity: minor cleanup

--- client/Social/Activity/views/activitylistcontainer.coffee
+++ client/Social/Activity/views/activitylistcontainer.coffee
@@ -27,11 +27,9 @@
       partial    : "Most Liked"

     @controller = new ActivityListController
-      delegate          : @
-      itemClass         : ActivityListItemView
-      showHeader        : yes
-      # wrapper           : no
-      # scrollView        : no
+      delegate   : @
+      itemClass  : ActivityListItemView
+      showHeader : yes

     @listWrapper = @controller.getView()
     @filterWarning = new FilterWarning


don't use client-side rendering for javascript/coffeescript with once=True, yet.

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -60,7 +60,7 @@

 CLIENT_SIDE_MODE_LINES = []
 for mode in ['md', 'html', 'coffeescript', 'javascript']
-    for s in ['', '(hide=false)', '(hide=true)']
+    for s in ['', '(hide=false)', '(hide=true)', '(once=false)']
         CLIENT_SIDE_MODE_LINES.push("%#{mode}#{s}")

 # Return true if there are currently unsynchronized changes, e.g., due to the network
@@ -1729,8 +1729,6 @@
         cm = @codemirror
         block = opts.block

-        console.log("execute_cell_client_side: block='#{cm.getRange({line:block.start,ch:0}, {line:block.end+1,ch:0})}'")
-
         # get the input text -- after the mode line
         input = cm.getRange({line:block.start+1,ch:0}, {line:block.end+1,ch:0})
         i = input.indexOf(MARKERS.output)
@@ -1741,9 +1739,6 @@
         else
             output_uuid        = misc.uuid()
             has_output_already = false
-
-        console.log("input = '#{input}'")
-        console.log("output_uuid = '#{output_uuid}'")

         # determine the mode
         i = opts.mode_line.indexOf('(')
@@ -1777,7 +1772,6 @@

         # update so that client sees rendering
         @process_sage_updates()
-

     split_cell_at: (pos) =>
         # Split the cell at the given pos.


teams: activity: emoji selector dropup default name fixed

- default icon set to :cow:
- default name made "Emoji Selector"
- selectedItem default made a String

--- client/activity/lib/components/emojiselector/index.coffee
+++ client/activity/lib/components/emojiselector/index.coffee
@@ -23,7 +23,7 @@
     items        : immutable.List()
     visible      : no
     itemsPerRow  : 8
-    selectedItem : immutable.Map()
+    selectedItem : ''

   componentDidMount: ->
@@ -106,12 +106,11 @@

-          {formatEmojiName selectedItem}
+          {formatEmojiName(selectedItem or 'cow')}

-          {formatEmojiName selectedItem}
+          {if selectedItem then formatEmojiName selectedItem else 'Choose your emoji!'}

     </Dropbox>
-


fix forEach

fix forEach

--- client/landing/app/MainApp/maincontroller/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainview.coffee
@@ -110,10 +110,12 @@
         appController = KD.getSingleton "kodingAppsController"
         appManifest = appController.constructor.manifests['AppWithMenu']

-        $.each appManifest.menu, (title, menu)->
-          appManifest.menu[title].callback = (contextmenu)->
+        appManifest.menu.forEach (item, index)->
+          item.callback = (contextmenu)->
             mainView = KD.getSingleton "mainView"
-            mainView.mainTabView.activePane?.mainView.emit "menu", title, contextmenu
+            view = mainView.mainTabView.activePane?.mainView
+            item.eventName or= item.title
+            view?.emit "menu.#{item.eventName}", item.eventName, item, contextmenu

         contextMenu = new JContextMenu
             event    : event


footer: revert generic footer

footer: revert generic footer

--- client/landing/site.landing/coffee/home/footerview.coffee
+++ client/landing/site.landing/coffee/home/footerview.coffee
@@ -14,11 +14,14 @@
     """

+        Pricing
+        Community
+        About
+        Legal
+
+
+
         Copyright © #{(new Date).getFullYear()} Koding, Inc
-        Showcase
-        Pricing
-        Help
-        Privacy Terms

     """


Keymap resolver prefers `ctrl-(`

Keymap resolver prefers `ctrl-(`

--- keymaps/git.cson
+++ keymaps/git.cson
@@ -1,6 +1,6 @@
 '.workspace':
   'ctrl-9': 'github:toggle-git-panel'
-  'ctrl-shift-9': 'github:toggle-git-panel-focus'
+  'ctrl-(': 'github:toggle-git-panel-focus' # ctrl-shift-9

 '.github-StagingView':
   'left': 'github:focus-diff-view'


patch for No-diff

patch for No-diff

--- lib/cli.iced
+++ lib/cli.iced
@@ -38,11 +38,14 @@

   options.color ?= tty.isatty(process.stdout.fd)

-  if options.raw
-    process.stderr.write "Serializing JSON output...\n"  if options.verbose
-    process.stdout.write JSON.stringify(result, null, 2)
+  if result
+    if options.raw
+      process.stderr.write "Serializing JSON output...\n"  if options.verbose
+      process.stdout.write JSON.stringify(result, null, 2)
+    else
+      process.stderr.write "Producing colored output...\n"  if options.verbose
+      process.stdout.write colorize(result, color: options.color)
   else
-    process.stderr.write "Producing colored output...\n"  if options.verbose
-    process.stdout.write colorize(result, color: options.color)
+    process.stderr.write "No diff" if options.verbose

   process.exit 1 if result


allverse/user: Fix bug where current table wasn't properly exposed to get().

--- Oghma/allverse/user.coffee
+++ Oghma/allverse/user.coffee
@@ -52,6 +52,7 @@
             box:      Oghma.Ext.Console.defaultBox
         zoom:      attrs.zoom ? O.table.defaultZoom
         ui_colors: attrs.ui_colors ? O.ui_colors.value().name
+        table:     attrs.table
       @__managed_windows =
          console: null
       @__update_guard = false


Prevent default for enter, tab, and comma

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -53,6 +53,7 @@
           self.closeDropdown()
           self.$input.val('')
         when keycodes.COMMA, keycodes.ENTER, keycodes.TAB
+          event.preventDefault()
           self.submit(self.$input.val())
           self.blurInput()
           self.closeDropdown()


#124 don't skip current item on `next-item` unless already exceeded

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -374,7 +374,12 @@
       @confirm(keepOpen: true)

   nextItem: ->
-    @confirmItemForDirection('next')
+    cursorPosition = atom.workspace.getActiveTextEditor().getCursorBufferPosition()
+    item = @getSelectedItem()
+    if item? and cursorPosition.isLessThan(item.range?.start ? item.point)
+      @confirm(keepOpen: true)
+    else
+      @confirmItemForDirection('next')

   previousItem: ->
     cursorPosition = atom.workspace.getActiveTextEditor().getCursorBufferPosition()


VirtualizationController: use ComputeProvider for create and remove vm

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -120,9 +120,18 @@
     , vmPrefix

   deleteVmByHostname: (hostnameAlias, callback, fireEvents = yes) ->
-    { JVM } = KD.remote.api
-
-    JVM.removeByHostname hostnameAlias, (err)->
+
+    # { JVM } = KD.remote.api
+    # JVM.removeByHostname hostnameAlias, (err)->
+
+    {ComputeProvider} = KD.remote.api
+
+    ComputeProvider.remove {
+      provider   : "koding"
+      credential : 1
+      hostnameAlias
+    }, (err) ->
+
       return callback err  if err

       if fireEvents
@@ -555,7 +564,16 @@
             color          : "white"
           duration         : 120000

-      JVM.createVmByNonce nonce, stackId, (err, vm) =>
+      # JVM.createVmByNonce nonce, stackId, (err, vm) =>
+
+      {ComputeProvider} = KD.remote.api
+
+      ComputeProvider.create {
+        provider   : "koding"
+        credential : 1
+        nonce, stackId
+      }, (err, vm) =>
+
         notify?.destroy()
         return  if KD.showError err


activity: restructure reactivity routes

activity: restructure reactivity routes

--- client/activity/lib/reactivityroutes.coffee
+++ client/activity/lib/reactivityroutes.coffee
@@ -17,21 +17,20 @@

 module.exports = [
   path: '/Channels'
+
   component: ActivityAppComponent
+
+  onLeave: ->
+    threadActions.changeSelectedThread null
+    messageActions.changeSelectedMessage null
+
   indexRoute:
     components:
       content: ChannelThreadPane
       modal: BrowsePublicChannelsModal
-  onLeave: ->
-    threadActions.changeSelectedThread null
-    messageActions.changeSelectedMessage null
+
   childRoutes: [
-    path: '/NewChannel'
-    components:
-      content: ChannelThreadPane
-      modal: CreatePublicChannelModal
-  ,
-    SingleChannelRoute
+    new SingleChannelRoute
   ]
 ,
   path: '/Messages'
@@ -41,12 +40,11 @@
       content: PrivateMessageThreadPane
       modal: BrowsePrivateChannelsModal
   childRoutes: [
+    new SinglePrivateMessageRoute
     path: '/NewMessage'
     components:
       content: PrivateMessageThreadPane
       modal: CreatePrivateChannelModal
-  ,
-    SinglePrivateMessageRoute
   ]
 ]


Removed debug logs

Removed debug logs

--- client/app/MainApp/paymentcontroller.coffee
+++ client/app/MainApp/paymentcontroller.coffee
@@ -242,7 +242,6 @@
       paymentModal = paymentController.createPaymentMethodModal {}, (newData, onError, onSuccess)->
         if type is 'group'
           group.setBillingInfo newData, (err, result)->
-            console.log "Group Billing:", arguments
             if err
               onError err
             else
@@ -250,7 +249,6 @@
               cb yes
         else
           KD.remote.api.JRecurlyPlan.setUserAccount newData, (err, result)->
-            console.log "User Billing:", arguments
             if err
               onError err
             else


IDE: Terminal: Add "open all" menu item to context menu

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -609,9 +609,16 @@

     terminalSessions = {}
     activeSessions   = []
-
+    inActiveSessions = []
+
+    # Collect active sessions
     frontApp.forEachSubViewInIDEViews_ 'terminal', (pane) =>
       activeSessions.push pane.remote.session  if pane.remote?
+
+    # Collect inactive sessions
+    for session in sessions
+      isActive = activeSessions.indexOf session
+      inActiveSessions.push session  if isActive is -1

     sessions.forEach (session, i) =>
       isActive = session in activeSessions
@@ -629,7 +636,11 @@

     terminalSessions["New Session"] =
       callback            : => @createTerminal { machine }
-      separator           : canTerminateSessions
+      separator           : (canTerminateSessions or inActiveSessions.length)
+
+    if inActiveSessions.length
+      terminalSessions['Open All']  =
+        callback          : => @openAllSessions { machine, inActiveSessions }

     if canTerminateSessions
       terminalSessions["Terminate all"] =
@@ -659,6 +670,14 @@
     return items

+  openAllSessions: (params) ->
+
+    { machine, inActiveSessions } = params
+
+    for session in inActiveSessions
+      @createTerminal { machine, session }
+
+
   createPlusContextMenu: ->

     return  if @isReadOnly


Tests: Teams: Helpers: remove cookie.

Tests: Teams: Helpers: remove cookie.

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -136,9 +136,6 @@

     browser.url url
     browser.maximizeWindow()
-
-    @setCookie(browser)
-    browser.url url

     @loginToTeam(browser, user)


Added hodor to spoiler words

Added hodor to spoiler words

--- src/scripts/constants.coffee
+++ src/scripts/constants.coffee
@@ -44,6 +44,7 @@
   'greyjoy'
   'gwendoline christie'
   'highgarden'
+  'hodor'
   'house bolton'
   'house stark'
   'house tyrell'
@@ -134,4 +135,4 @@
   'asongofmemesandrage'
   'earthoficeandfire'
 ]
-GOT_SUBREDDITS_REGEX = new RegExp('(\/r\/)' + GOT_RELATED_SUBREDDITS.join('|'), 'i')+GOT_SUBREDDITS_REGEX = new RegExp('(\/r\/)' + GOT_RELATED_SUBREDDITS.join('|'), 'i')


trigger lazyload event by application

trigger lazyload event by application

--- src/scripts/main/main.coffee
+++ src/scripts/main/main.coffee
@@ -44,6 +44,12 @@
       else if navbar_top >= scroll
         $("#navbar").removeClass "follow_scroll"

+      if navigator.userAgent.match(/(iPhone|iPod|iPad|Mobile|Android)/g)
+        $("img.lazy").each ->
+          self = @
+          if $(self).offset().top <= scroll
+            $(self).trigger "appear"
+
     status =
       rank_5:"show"
       rank_4:"show"


make it easier on GC

make it easier on GC

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -262,31 +262,31 @@
 									# if not, increment scrollTop
 									viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true))

+						doAdjustment = (rid, scrolling, finalize)->
+							log "top {actual=#{adapter.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{adapter.bottomDataPos()}}"
+							if shouldLoadBottom()
+								enqueueFetch(rid, true, scrolling)
+							else
+								enqueueFetch(rid, false, scrolling) if shouldLoadTop()
+							finalize(rid) if finalize
+							if pending.length == 0
+								topHeight = 0
+								for item in buffer
+									itemHeight = item.element.outerHeight(true)
+									if adapter.topDataPos() + topHeight + itemHeight < topVisiblePos()
+										topHeight += itemHeight
+									else
+										topVisible(item)
+										break
+
 						adjustBuffer = (rid, scrolling, newItems, finalize)->
-							doAdjustment = ->
-								log "top {actual=#{adapter.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{adapter.bottomDataPos()}}"
-								if shouldLoadBottom()
-									enqueueFetch(rid, true, scrolling)
-								else
-									enqueueFetch(rid, false, scrolling) if shouldLoadTop()
-								finalize(rid) if finalize
-								if pending.length == 0
-									topHeight = 0
-									for item in buffer
-										itemHeight = item.element.outerHeight(true)
-										if adapter.topDataPos() + topHeight + itemHeight < topVisiblePos()
-											topHeight += itemHeight
-										else
-											topVisible(item)
-											break
-
 							if newItems
 								$timeout ->
 									for row in newItems
 										adjustRowHeight row.appended, row.wrapper
-									doAdjustment()
-							else
-								doAdjustment()
+									doAdjustment(rid, scrolling, finalize)
+							else
+								doAdjustment(rid, scrolling, finalize)

 						finalize = (rid, scrolling, newItems)->
 							adjustBuffer rid, scrolling, newItems, ->


Slider Component: codefix

Slider Component: codefix

--- components/sliderbar/KDSliderBarView.coffee
+++ components/sliderbar/KDSliderBarView.coffee
@@ -76,7 +76,7 @@
     if name?
       for handle in @handles
         return handle if handle.getOption('name') is name
-      return console.error "Can't find a handle named #{name}"
+      return error "Can't find a handle named #{name}"

   setLimits:->
     {maxValue, minValue, interval} = @getOptions()
@@ -97,7 +97,6 @@
     @setLimits()
     @drawBar()   if @getOption('drawBar')
     @addLabels() if @getOption('showLabels')
-    window.asd = @

 class KDSliderBarHandleView extends KDCustomHTMLView
   constructor:(options = {}, data = {})->


'Updating for version 0.12.5dev4'

'Updating for version 0.12.5dev4'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.5dev3'
+export version = '0.12.5dev4'


Remove superfluous semicolon... again

Remove superfluous semicolon... again

--- resources/assets/coffee/react/beatmap-discussions/discussions.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussions.coffee
@@ -170,7 +170,7 @@
     className += ' u-hide-by-height' unless visible

     if discussion.parent_id?
-      parentDiscussion = _.find(@props.currentDiscussions.reviews, {id: discussion.parent_id});
+      parentDiscussion = _.find(@props.currentDiscussions.reviews, {id: discussion.parent_id})

     div
       key: discussion.id


Always show the date instead of countdown

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -202,9 +202,9 @@
                       reason: disqualification.reason ? osu.trans('beatmaps.nominations.disqualifed_no_reason')
               else if mapIsQualified
                 if rankingETA
-                  span dangerouslySetInnerHTML:
-                    __html: osu.trans 'beatmaps.nominations.qualified',
-                      date: osu.timeago(rankingETA)
+                  span null,
+                    osu.trans 'beatmaps.nominations.qualified',
+                      date: moment(rankingETA).format(dateFormat)
                 else
                   span null, osu.trans 'beatmaps.nominations.qualified-soon'


Graph Refactoring : change main feed to activity

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -149,7 +149,7 @@

   fetchAll:(requestOptions, callback)->
     requestOptions.facet = @facets
-    mainFeed = require "./mainfeed"
+    mainFeed = require "./activity"
     mainFeed.fetchAll requestOptions, callback

   fetchReplies: (itemId, callback)->


remove unused event listeners

remove unused event listeners

--- src/desktop/components/artist_page_cta/view.coffee
+++ src/desktop/components/artist_page_cta/view.coffee
@@ -36,8 +36,6 @@
     @signupIntent = 'Artist CTA Banner'
     @afterAuthPath = window.location
     @$window.on 'scroll', _.once(@maybeShowOverlay)
-    mediator.on 'clickFollowButton', @fullScreenOverlay
-    mediator.on 'clickHeaderAuth', @fullScreenOverlay
     mediator.on("artist_cta:account_creation", (options) => analytics.track("Created Account", options))

   maybeShowOverlay: () =>


[skip] Removed redundant definition of 'timestamp' parser (1m)

--- app/assets/javascripts/houston-scheduler/app.coffee
+++ app/assets/javascripts/houston-scheduler/app.coffee
@@ -25,12 +25,6 @@
   type: 'numeric'

 $.tablesorter.addParser
-  id: 'timestamp'
-  is: (s)-> false # don't auto-detect
-  format: (text, table, td)-> $(td).attr('data-timestamp')
-  type: 'text'
-
-$.tablesorter.addParser
   id: 'sequence'
   is: (s)-> false # don't auto-detect
   format: (text, table, td)->


dont initialize twice the plugins to avoid loosing all your selectize data when adding a nested resource in your form

--- app/assets/javascripts/rails_admin_selectize.coffee
+++ app/assets/javascripts/rails_admin_selectize.coffee
@@ -1,5 +1,7 @@
 class RailsAdminSelectize
   constructor: (@$el) ->
+    console.log('FOOOOO', @$el)
+
     @single = false
     @el = @$el[0]

@@ -68,4 +70,9 @@

 $(document).on "rails_admin.dom_ready", ->
-  $('[data-selectize]').each (i, el) -> new RailsAdminSelectize($(el))
+  $('[data-selectize]').each (i, el) ->
+    $select = $(el)
+    unless $select.data('rails_admin_selectize')
+      instance = new RailsAdminSelectize($select)
+      $select.data('rails_admin_selectize', instance)
+