Toolbar button Help link bug fix

Toolbar button Help link bug fix

--- extension/packages/button.coffee
+++ extension/packages/button.coffee
@@ -2,6 +2,7 @@
 , setPref } = require 'prefs'

 { showHelp } = require 'help'
+{ commandsHelp } = require 'commands'

 positions = {}

@@ -108,7 +109,7 @@

   onHelpCommand = (event) ->
     if tabWindow = window.gBrowser.selectedTab.linkedBrowser.contentWindow
-      showHelp tabWindow.document
+      showHelp tabWindow.document, commandsHelp

     event.stopPropagation()


BUGFIX: Added Mac OS X Dashboard web clip support to the browser-check HTTP middleware.

--- lib/http_middleware/browser_check.coffee
+++ lib/http_middleware/browser_check.coffee
@@ -37,7 +37,7 @@

     # Capture headless browsers (e.g. spiders)
     return true unless ua?
-
+
     # Allow Chrome version 4 and above
     if ua.match(/Chrom(e|ium)/)
       re = new RegExp("Chrom(e|ium)/([0-9]{1,})")
@@ -52,6 +52,11 @@
         version = parseFloat( RegExp.$1 )
         return false if version >= 5

+    # Mac OS X Dashboard Web Clip
+    return false if ua.match(/WebClip/)
+    # TODO - find out what version of AppleWebKit (~ 533) implemented websockets,
+    # will allow for greater refactoring of code above.
+
     # Else show the Browser Incompatible page
     true


Add --atom-child_process-fork if running from Atom Helper

Temporary workaround to allow packages to be installed/uninstalled
from the Atom settings view.

--- src/command.coffee
+++ src/command.coffee
@@ -19,4 +19,10 @@

   fork: (script, args, remaining...) ->
     args.unshift(script)
+
+    # FIXME temporary hack until https://github.atom/atom-shell/issues/83 is
+    # resolved
+    if /Atom Helper$/.test process.execPath
+      args.unshift('--atom-child_process-fork')
+
     @spawn(process.execPath, args, remaining...)


mask user inputted env vars on the assumption they're probably private

--- src/decking.coffee
+++ src/decking.coffee
@@ -369,6 +369,8 @@
         # we need to ask the user for it
         options =
           prompt: "#{object.name} requires a value for the env var '#{key}':"
+          silent: true
+          replace: "*"

         read options, (err, value) ->
           arg = [].concat arg, ["-e #{key}=#{value}"]


add more info to status output when containers are running

--- src/decking.coffee
+++ src/decking.coffee
@@ -204,7 +204,6 @@

   attach: (cluster, done) ->

-
     timeout = 600

     reAttach = (name, container, attempts = 0) ->
@@ -257,7 +256,16 @@
         if err # @TODO inspect
           logAction name, "does not exist"
         else if data.State.Running
-          logAction name, "running" # @TODO more details
+          str = "running  "
+          ip = data.NetworkSettings.IPAddress
+          str += ip
+          for local,host of data.NetworkSettings.Ports
+            host = host?[0]
+            str += "  "
+            if host
+              str += "#{host.HostIp}:#{host.HostPort}->"
+            str += local
+          logAction name, str
         else
           logAction name, "stopped"


AvatarMenu: removed go back to old koding link

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -106,31 +106,31 @@
             KD.getSingleton('router').handleRoute '/Dashboard'
             @hide()

-    @avatarPopupContent.addSubView new KDCustomHTMLView
-      tagName    : 'a'
-      attributes : href : '#'
-      cssClass   : 'bottom'
-      partial    : 'Go back to old Koding'
-      click      : (event)=>
-        KD.utils.stopDOMEvent event
-        modal = new KDModalView
-          title   : "Go back to old Koding"
-          cssClass: "go-back-survey"
-          content : """
-            Please take a short survey about New Koding.

-            """
-          buttons :
-            "Switch":
-              cssClass: "modal-clean-gray"
-              callback: ->
-                KD.mixpanel "Switch to old Koding, click"
-                KD.utils.goBackToOldKoding()
-                modal.destroy()
-            "Cancel":
-              cssClass: "modal-cancel"
-              callback: ->
-                modal.destroy()
-        @hide()
+    # @avatarPopupContent.addSubView new KDCustomHTMLView
+    #   tagName    : 'a'
+    #   attributes : href : '#'
+    #   cssClass   : 'bottom'
+    #   partial    : 'Go back to old Koding'
+    #   click      : (event)=>
+    #     KD.utils.stopDOMEvent event
+    #     modal = new KDModalView
+    #       title   : "Go back to old Koding"
+    #       cssClass: "go-back-survey"
+    #       content : """
+    #         Please take a short survey about New Koding.

+    #         """
+    #       buttons :
+    #         "Switch":
+    #           cssClass: "modal-clean-gray"
+    #           callback: ->
+    #             KD.mixpanel "Switch to old Koding, click"
+    #             KD.utils.goBackToOldKoding()
+    #             modal.destroy()
+    #         "Cancel":
+    #           cssClass: "modal-cancel"
+    #           callback: ->
+    #             modal.destroy()
+    #     @hide()

     @avatarPopupContent.addSubView new KDCustomHTMLView
       tagName    : 'a'


MachineSettingsDomainsView: Create add view at the bottom.

--- client/app/lib/providers/machinesettingsdomainsview.coffee
+++ client/app/lib/providers/machinesettingsdomainsview.coffee
@@ -24,6 +24,13 @@
     @listController.getListView()
       .on 'DeleteDomainRequested', @bound 'removeDomain'
       .on 'DomainStateChanged',    @bound 'handleStateChange'
+
+
+  createElements: ->
+
+    @createHeader()
+    @createListView()
+    @createAddView()

   createAddInput: ->


change ap for L2 and L3

change ap for L2 and L3


Better event handling

Better event handling

--- main/static/src/script/common/upload.coffee
+++ main/static/src/script/common/upload.coffee
@@ -81,14 +81,15 @@
       xhr.upload.addEventListener 'progress', (event) ->
         progress(parseInt(event.loaded / event.total * 100.0))

-
-      xhr.onload = (event) =>
-        response = JSON.parse(event.currentTarget.response)
-        progress(100.0, response.result)
-        @active_files -= 1
-      xhr.onerror = () =>
-        progress(0, undefined, 'error')
-        @active_files -= 1
+      xhr.onreadystatechange = (event) =>
+        if xhr.readyState == 4
+          if xhr.status == 200
+            response = JSON.parse(xhr.responseText)
+            progress(100.0, response.result)
+            @active_files -= 1
+          else
+            progress(0, undefined, 'error')
+            @active_files -= 1

       xhr.open('POST', url, true)
       data = new FormData()


removed undefined and thus noop callbacks from postmessage nativecall

--- src/PostMessage.coffee
+++ src/PostMessage.coffee
@@ -1,17 +1,14 @@
 class PostMessage

   @postMessage: (message, targetOrigin) =>
-
-    callbacks = {}
-
     escapedJSONMessage = escape(JSON.stringify(message))

     steroids.nativeBridge.nativeCall
       method: "broadcastJavascript"
       parameters:
         javascript: "steroids.PostMessage.dispatchMessageEvent('#{escapedJSONMessage}', '*');"
-      successCallbacks: [callbacks.onSuccess]
-      recurringCallbacks: [callbacks.onFailure]
+      successCallbacks: []
+      recurringCallbacks: []


StacksCustomViews: setGroupTemplate helper for setting the current group template

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -56,6 +56,28 @@
         title : title or "Default stack template"
         template, machines, credentials
       }, callback
+
+
+  setGroupTemplate = (stackTemplate, callback) ->
+
+    { groupsController } = kd.singletons
+
+    currentGroup = groupsController.getCurrentGroup()
+    { slug }     = currentGroup
+
+    if slug is 'koding'
+      message = 'Setting stack template for koding is disabled'
+      new kd.NotificationView title: message
+      return callback {message}
+
+    currentGroup.modify stackTemplates: [ stackTemplate._id ], (err) ->
+      return callback err  if err
+
+      new kd.NotificationView
+        title: "Group (#{slug}) stack has been saved!"
+
+      callback()
+

   fetchAndShowCredentialData = (credential, outputView) ->


Sidebar: Don't draw sidebar when account added to channel.

We are now handling sidebar redrawing with a different way which is lead by computecontroller.reset. This line was the reason of multiple machine share popups. I tested it widely it's not the case anymore but I am not sure removing this line is 100% sure. So I will leave it as commented for a while. If we start to show missing or not removing machines on sidebar when collaboration started or ended this line might be the reason. If you blame this line and see my name

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -264,7 +264,7 @@

       if isChannelCollaborative channel
         @fetchEnvironmentData (data) =>
-          @sharedMachinesList.updateList data.shared.concat data.collaboration
+          # @sharedMachinesList.updateList data.shared.concat data.collaboration
           @setWorkspaceUnreadCount channel, unreadCount
       else
         item = @addItem channel, index


Use Encoded version of fullName

Use Encoded version of fullName

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -6,6 +6,7 @@

   defaultManifest = (type, name)->
     {profile} = KD.whoami()
+    fullName = Encoder.htmlDecode "#{profile.firstName} #{profile.lastName}"
     raw =
       devMode       : yes
       version       : "0.1"
@@ -13,7 +14,7 @@
       identifier    : "com.koding.apps.#{__utils.slugify name or type}"
       path          : "~/Applications/#{name or type.capitalize()}.kdapp"
       homepage      : "#{profile.nickname}.koding.com/#{__utils.slugify name or type}"
-      author        : "#{profile.firstName} #{profile.lastName}"
+      author        : "#{fullName}"
       repository    : "git://github.com/#{profile.nickname}/#{__utils.slugify name or type}.kdapp.git"
       description   : "#{name or type} : a Koding application created with the #{type} template."
       category      : "web-app" # can be web-app, add-on, server-stack, framework, misc


landing: fixed more grammar errors

landing: fixed more grammar errors

--- client/landing/site.landing/coffee/home/AppView.coffee
+++ client/landing/site.landing/coffee/home/AppView.coffee
@@ -24,7 +24,7 @@
       },
       {
         position : [55, 6] #percent
-        content  : 'If one IDE tab is not enough, open up more!'
+        content  : 'The IDE supports multi-tab layouts so that you \n can have all your files open and within easy reach.'
       },
       {
         position : [55, 32] #percent
@@ -39,11 +39,11 @@
     [
       {
         position : [55, 6] #percent
-        content  : 'Terminals can be opened vertically or horizontally. \n You can even split them!'
+        content  : 'Terminal and IDE tabs can be opened vertically or \n horizontally. You can even split them!'
       },
       {
         position : [54, 51] #percent
-        content  : 'Open as many terminals as you like!'
+        content  : 'You can open as many Terminal tabs as you want \n and keep an eye on all those scripts.'
       },
       {
         position : [65, 65] #percent
@@ -66,11 +66,11 @@
       },
       {
         position : [88, 38] #percent
-        content  : 'Easily see which topics are of interest to the Koding Community.'
+        content  : 'Easily see which top topics are of interest to \n the Koding Community.'
       },
       {
         position : [9, 44] #percent
-        content  : 'If a topic of discussion is for your team only, \n then easily take it to a private message chat.'
+        content  : 'If a topic of discussion is for your team only, \n then easily take it to a private message.'
       }
     ]
     #Settings
@@ -81,15 +81,15 @@
       },
       {
         position : [40, 24] #percent
-        content  : 'All Koding VMs come with public IPs!'
+        content  : 'All Koding VMs come with public IPs. \n Very useful when using FTP, SSH, etc.'
       },
       {
         position : [42, 41] #percent
-        content  : 'Easy links to helpful guides that show you how \n you can connect to your VM using ssh, ftp and much more!'
+        content  : 'Easy links to helpful guides that show you how \n you can connect to your VM using SSH, FTP and much more!'
       },
       {
         position : [37, 53] #percent
-        content  : 'If you want, you can keep your VM running all the time.'
+        content  : 'If you prefer, you can keep your VM running all the time by upgrading.'
       },
       {
         position : [20, 58] #percent


Fix typo.

Fix typo.

--- cms/static/coffee/spec/views/metadata_edit_spec.coffee
+++ cms/static/coffee/spec/views/metadata_edit_spec.coffee
@@ -376,10 +376,10 @@
           @el.find('input').last().trigger('input')
           expect(@el.find('.create-setting')).not.toHaveClass('is-disabled')

-      describe "MetadataView.IsoTime allows the user to enter time string in HH:mm:ss format", ->
+      describe "MetadataView.RelativeTime allows the user to enter time string in HH:mm:ss format", ->
           beforeEach ->
               model = new MetadataModel(timeEntry)
-              @view = new MetadataView.IsoTime({model: model})
+              @view = new MetadataView.RelativeTime({model: model})

           it "uses a text input type", ->
               assertInputType(@view, 'text')


Fix typo.

Fix typo.

--- cms/static/coffee/spec/views/metadata_edit_spec.coffee
+++ cms/static/coffee/spec/views/metadata_edit_spec.coffee
@@ -85,10 +85,10 @@
           default_value: "00:00:00",
           display_name: "Time",
           explicitly_set: true,
-          field_name: "iso_time",
+          field_name: "relative_time",
           help: "Specifies the name for this component.",
           options: [],
-          type: MetadataModel.ISO_TIME_TYPE,
+          type: MetadataModel.RELATIVE_TIME_TYPE,
           value: "12:12:12"
       }


fix errors on version check

fix errors on version check

--- src/ui/version.coffee
+++ src/ui/version.coffee
@@ -8,6 +8,7 @@

 check = ->
   request.get options,  (err, res, body) ->
+    return console.log err if err
     body = JSON.parse body
     tag = body.tag_name
     releasedVersion = tag.substr(1) # remove first "v" char


Updated default hostname from localhost to 0.0.0.0

--- includes/config-settings.coffee
+++ includes/config-settings.coffee
@@ -5,7 +5,7 @@

 	## Server
 	protocol: 'http'                             # Using `https` requires valid certificates.
-	hostname: 'localhost'                        # Can be 0.0.0.0 for binding to all ports.
+	hostname: '0.0.0.0'                          # Can be 0.0.0.0 for binding to all ports.
 	port: 3000                                   # Port of webserver.
 	# proxyPort: 3001                            # Optional. Will be `port + 1` if not defined.


update atom config

update atom config

--- .atom/config.cson
+++ .atom/config.cson
@@ -2,7 +2,8 @@
   Zen: {}
   "atom-alignment": {}
   "atom-beautify":
-    _analyticsUserId: ""
+    general:
+      _analyticsUserId: ""
   "autocomplete-plus": {}
   "autocomplete-ruby":
     rsensePath: "/Users/benbalter/.rbenv/shims/rsense"
@@ -18,10 +19,11 @@
     ]
     excludeVcsIgnoredPaths: false
     telemetryConsent: "no"
+    titleBar: "custom-inset"
   editor:
     backUpBeforeSaving: true
     fontFamily: "Inconsolata"
-    fontSize: 19
+    fontSize: 20
     invisibles: {}
     showIndentGuide: true
     showInvisibles: true


atom: update config

atom: update config

--- .atom/config.cson
+++ .atom/config.cson
@@ -44,6 +44,7 @@
     ]
     packagesWithKeymapsDisabled: [
       "git-time-machine"
+      "linter-eslint"
     ]
     projectHome: "~/dev"
     themes: [
@@ -89,7 +90,16 @@
   minimap: {}
   "one-dark-ui":
     layoutMode: "Compact"
+  "split-diff":
+    diffWords: true
+    ignoreWhitespace: true
+    leftEditorColor: "red"
+    rightEditorColor: "green"
+    syncHorizontalScroll: true
   "symbols-tree-view": {}
+  "symbols-view": {}
+  "tool-bar":
+    position: "Right"
   "tree-view":
     hideIgnoredNames: true
   "vim-mode":


Add check to see if getPath function exists.

--- lib/controllers/config-controller.coffee
+++ lib/controllers/config-controller.coffee
@@ -69,7 +69,7 @@
     filePath = null;
     pane = atom.workspace.getActivePaneItem();

-    if pane?
+    if typeof pane?.getPath is "function"
       filePath = pane.getPath();

     @runProcessWithFile(filePath);


fix(baseService): use limit of 20 if limit is 0

Since the API was changed, so that limit=0 won't return any results
closes #106

--- src/coffee/services/base.coffee
+++ src/coffee/services/base.coffee
@@ -335,6 +335,10 @@
       endpoint = @constructor.baseResourceEndpoint
       originalQuery = @_params.query
       originalPredicate = @_params.query.where
+
+      if originalQuery.perPage is 0
+        debug 'using batch size of 20 since 0 wont return any results'
+        originalQuery.perPage = 20

       _processPage = (lastId, acc = []) =>
         debug 'processing next page with id: %s', lastId


Organization documentation

Organization documentation

--- lib/layervault/nodes/organization.coffee
+++ lib/layervault/nodes/organization.coffee
@@ -1,8 +1,13 @@
 Node = require '../node'
 Project = require './project'

+# A single LayerVault Organization. The starting point for all
+# API queries.
 module.exports = class Organization extends Node
   relations:
     projects: Project

+  # Retrieves information about this organization.
+  #
+  # @param [Function] cb The finished callback.
   get: (cb) -> @api.get @nodePath, {}, @buildRelations(cb)


review linked to class averages

review linked to class averages

--- src/components/performance.cjsx
+++ src/components/performance.cjsx
@@ -88,32 +88,40 @@
         id: heading.plan_id
         periodIndex: @state.periodIndex
         courseId: @props.courseId
+      linkText = 'Review'
+      linkText = heading.average.toFixed(2) if heading.average?

       linkToPlanSummary =
         <Router.Link
           to='reviewTaskPeriod'
           params={linkParams}
           className='review-plan'>
-            <i className='fa fa-eye'></i>
+            {linkText}
         </Router.Link>

-    customHeader =
+    customHeader = linkToPlanSummary
+    customHeader ?= 'Class Average'
+
+    customGroupHeader =
       <div
         dataKey={i}
         onClick={@sortClick.bind(@, i, classes)}
         className={'header-cell ' + classes}>
           {heading.title}
-          {linkToPlanSummary}
       </div>

-    <Column
-    label={heading.title}
-    headerRenderer={-> customHeader}
-    cellRenderer={-> @cellData}
-    width={@state.colSetWidth}
-    fixed={fixed}
-    isResizable=false
-    dataKey={i} />
+    <ColumnGroup
+      groupHeaderRenderer={-> customGroupHeader}
+      fixed={fixed}>
+      <Column
+      label={heading.title}
+      headerRenderer={-> customHeader}
+      cellRenderer={-> @cellData}
+      width={@state.colSetWidth}
+      fixed={fixed}
+      isResizable=false
+      dataKey={i} />
+    </ColumnGroup>

   renderAverageCell: (heading) ->
     if heading.class_average
@@ -202,7 +210,8 @@
           rowsCount={sortData.length}
           width={@state.tableWidth}
           height={@state.tableHeight}
-          headerHeight={50}>
+          headerHeight={50}
+          groupHeaderHeight={50}>

           {headings}
         </Table>


Fixed log mapper code

Fixed log mapper code

--- bokehjs/src/coffee/mapper/1d/log_mapper.coffee
+++ bokehjs/src/coffee/mapper/1d/log_mapper.coffee
@@ -44,7 +44,7 @@
           for x, idx in intermediate
             if isNaN(intermediate[idx])
               throw "NaN"
-            if isFinite(intermediate[idx]) == False
+            if isFinite(intermediate[idx]) == false
               throw "Infinite"

         catch error
@@ -56,15 +56,18 @@
       return result

     map_from_target: (xprime) ->
-      [scale, offset] = @get('mapper_state')
-      return (xprime - offset) / scale
+      [scale, offset, inter_scale, inter_offset] = @get('mapper_state')
+      intermediate = (xprime - offset) / scale
+      intermediate = Math.exp(inter_scale * intermediate + inter_offset)
+
+      return intermediate

     v_map_from_target: (xprimes) ->
-      [scale, offset] = @get('mapper_state')
-      result = new Float64Array(xprimes.length)
-      for xprime, idx in xprimes
-        result[idx] = (xprime - offset) / scale
-      return result
+      [scale, offset, inter_scale, inter_offset] = @get('mapper_state')
+      intermediate = xprimes.map (i) -> (i - offset) / scale
+      intermediate = intermediate.map (i) -> Math.exp(inter_scale * i + inter_offset)
+
+      return intermediate

     _get_safe_scale: (orig_start, orig_end) ->
       if orig_start < 0
@@ -108,7 +111,7 @@
       #scale = (target_end - target_start)/(source_end - source_start)
       #offset = -(scale * source_start) + target_start

-      screen_range = target_start - target_end
+      screen_range = target_end - target_start

       [start, end] = @_get_safe_scale(source_start, source_end)

@@ -123,7 +126,6 @@
       offset = target_start
       return [scale, offset, inter_scale, inter_offset]

-
   class LogMappers extends Backbone.Collection
     model: LogMapper


Pass arguments to modes' onExit handlers.

--- content_scripts/mode.coffee
+++ content_scripts/mode.coffee
@@ -187,12 +187,12 @@
   onExit: (handler) ->
     @exitHandlers.push handler

-  exit: ->
+  exit: (args...) ->
     if @modeIsActive
       @log "deactivate:", @id
       unless @modeIsExiting
         @modeIsExiting = true
-        handler() for handler in @exitHandlers
+        handler args... for handler in @exitHandlers
         handlerStack.remove handlerId for handlerId in @handlers
       Mode.modes = Mode.modes.filter (mode) => mode != @
       @modeIsActive = false


Change Atom

Change Atom

--- config/atom/config.cson
+++ config/atom/config.cson
@@ -27,7 +27,6 @@
     fontSize: 10
     invisibles: {}
     lineHeight: 1.2
-    showIndentGuide: true
     zoomFontWhenCtrlScrolling: true
   "exception-reporting":
     userId: "4da6c538-236c-f6aa-82ca-b2a8d82bd5a0"
@@ -42,7 +41,7 @@
   nuclide:
     remoteProjectsConfig: []
   "one-dark-ui":
-    fontSize: 9
+    fontSize: 10
   racer:
     racerBinPath: "/Users/rajatv/.multirust/toolchains/beta/cargo/bin/racer"
     rustSrcPath: "/Users/rajatv/work/private/tryouts/rust/rust/src"


remove pistachios from kd: WIP

remove pistachios from kd: WIP

--- client/ide/filetree/itemviews/fileitem.coffee
+++ client/ide/filetree/itemviews/fileitem.coffee
@@ -1,4 +1,6 @@
 class NFileItemView extends KDCustomHTMLView
+
+  JView.mixin @prototype

   loaderRequiredEvents = ['job', 'remove', 'save', 'saveAs']


Do not remove permanent machine nodes

Do not remove permanent machine nodes

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -854,7 +854,7 @@
       @chat = null
       @statusBar.emit 'CollaborationEnded'
       @removeParticipant nick()
-      @removeMachineNode()
+      @removeMachineNode()  unless @mountedMachine.isPermanent()

     @rtm.once 'RealtimeManagerDidDispose', =>
       method = switch


save for FSFolder creates a directory for it as expected

--- client/landing/app/MainApp/fs/fsfolder.coffee
+++ client/landing/app/MainApp/fs/fsfolder.coffee
@@ -20,3 +20,21 @@
       else
         @emit "fs.fetchContents.finished", err
       callback? err, files
+
+  save:(callback)->
+
+    @emit "fs.save.started"
+
+    @kiteController.run
+      kiteName  : 'os'
+      method    : 'fs.createDirectory'
+      withArgs  : {@path}
+    , (err, res)=>
+
+      if err then warn err
+      @emit "fs.save.finished", err, res
+      callback? err, res
+
+  saveAs:(callback)->
+    log 'Not implemented yet.'
+    callback? null


Add new command to list available templates

--- src/memegen.coffee
+++ src/memegen.coffee
@@ -11,6 +11,7 @@
 #
 # Commands:
 #   hubot memegen <template_id> <phrase> - Create the meme usign the template_id and the phrase given
+#   hubot memegen list templates - List available templates
 #
 # Author:
 #   basoko
@@ -110,3 +111,9 @@
   robot.respond /memegen (.*?) (.*)$/i, (msg) ->
     meme = new Meme apikey, msg
     meme.generate()
+
+  robot.respond /memegen list templates$/i, (msg) ->
+    response = 'Current templates available:\n'
+    response += "#{name} (#{value.file})\n" for name, value of templates
+
+    msg.send response


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -23,7 +23,7 @@
 # => Sets up a health meter that will be drawn during the player overlay event

 enemy.meter
-'health'
+  na'health'
 # => Sets up a health meter that will be drawn during the enemy overlay event.
 # Since maxHealth wasn't provided, it is set to the value of I.health (500)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -15,7 +15,7 @@
 enemy.include Metered

 player.meter 'health'
-# => Sets up a health meter that will be drawn during
+# => Sets up a health meter that will be drawn during the player overlay event

 player.include(Bounded)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -126,7 +126,7 @@
   @param {Number} height Height of the meter
   @param {Object} position An x, y object representing the position of the meter
   @param {Number} radius Border radius of the meter
-    showName - Boolean to toggle whether or not to show the attribute associated with the meter
+  @param {Boolean} showName - Boolean to toggle whether or not to show the attribute associated with the meter
     text - A String to display over the meter. Overrides default name attribute
     width - How wide the meter is
   ###


fix error event fullpageformview

fix error event fullpageformview

--- BackofficeBundle/Resources/public/coffee/FullPageFormView.coffee
+++ BackofficeBundle/Resources/public/coffee/FullPageFormView.coffee
@@ -29,13 +29,12 @@
       return
     return

-  addEventOnForm: (e)->
+  addEventOnForm: (event)->
     event.preventDefault()
     viewContext = @
     $("form", @$el).ajaxSubmit
-      context: button: $(".submit_form",e.target).parent()
+      context: button: $(".submit_form",event.target).parent()
       success: (response) ->
-        viewContext.options.html = response
         new FullPageFormView(viewContext.addOption(
           html: response
         ))


Fixed bulk action (selector for checked tickets was wrong).

--- app/assets/javascripts/app/controllers/ticket_overview.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.coffee
@@ -278,13 +278,12 @@
       )
       table = $(table)
       table.delegate('[name="bulk_all"]', 'click', (e) ->
-        console.log('OOOO',  $(e.target).attr('checked') )
         if $(e.target).attr('checked')
           $(e.target).closest('table').find('[name="bulk"]').attr('checked', true)
         else
           $(e.target).closest('table').find('[name="bulk"]').attr('checked', false)
       )
-      @el.find('.table-overview').append(table)
+      @$('.table-overview').append(table)
     else
       openTicket = (id,e) =>

@@ -316,7 +315,7 @@
           id: refObject.id
         value
       callbackCheckbox = (id, checked, e) =>
-        if @el.find('table').find('input[name="bulk"]:checked').length == 0
+        if @$('table').find('input[name="bulk"]:checked').length == 0
           @bulkForm.hide()
         else
           @bulkForm.show()
@@ -384,44 +383,39 @@
     @organizationPopups()

     @bulkForm = new BulkForm
-      holder: @el
+      holder: @el
+      view: @view

     # start bulk action observ
     @el.append( @bulkForm.el )
-    if @el.find('.table-overview').find('input[name="bulk"]:checked').length isnt 0
+    if @$('.table-overview').find('input[name="bulk"]:checked').length isnt 0
       @bulkForm.show()

     # show/hide bulk action
-    @el.find('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'click', (e) =>
-      console.log('YES')
-      if @el.find('.table-overview').find('input[name="bulk"]:checked').length == 0
-
-        # hide
+    @$('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'click', (e) =>
+      if @$('.table-overview').find('input[name="bulk"]:checked').length == 0
         @bulkForm.hide()
-
         @bulkForm.reset()
       else
-
-        # show
         @bulkForm.show()
     )

     # deselect bulk_all if one item is uncheck observ
-    @el.find('.table-overview').delegate('[name="bulk"]', 'click', (e) ->
+    @$('.table-overview').delegate('[name="bulk"]', 'click', (e) ->
       if !$(e.target).attr('checked')
         $(e.target).parents().find('[name="bulk_all"]').attr('checked', false)
     )

   getSelected: ->
     @ticketIDs = []
-    @el.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
+    @$('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
       ticket_id = $(element).val()
       @ticketIDs.push ticket_id
     )
     @ticketIDs

   setSelected: (ticketIDs) ->
-    @el.find('.table-overview').find('[name="bulk"]').each( (index, element) ->
+    @$('.table-overview').find('[name="bulk"]').each( (index, element) ->
       ticket_id = $(element).val()
       for ticket_id_selected in ticketIDs
         if ticket_id_selected is ticket_id
@@ -448,6 +442,7 @@

   events:
     'submit form':       'submit'
+    'click .js-submit':  'submit'
     'click .js-confirm': 'confirm'
     'click .js-cancel':  'reset'

@@ -472,19 +467,19 @@
     @html App.view('agent_ticket_view/bulk')()

     new App.ControllerForm(
-      el: @el.find('#form-ticket-bulk')
+      el: @$('#form-ticket-bulk')
       model:
         configure_attributes: @configure_attributes_ticket
         className:            'create'
         labelClass:           'input-group-addon'
-      form_data:   @bulk
+      form_data:  @bulk
       noFieldset: true
     )

     new App.ControllerForm(
-      el: @el.find('#form-ticket-bulk-comment')
+      el: @$('#form-ticket-bulk-comment')
       model:
-        configure_attributes: [{ name: 'body',         display: 'Comment', tag: 'textarea', rows: 4, null: true, upload: false, item_class: 'flex' }]
+        configure_attributes: [{ name: 'body', display: 'Comment', tag: 'textarea', rows: 4, null: true, upload: false, item_class: 'flex' }]
         className:            'create'
         labelClass:           'input-group-addon'
       form_data:   @bulk
@@ -492,12 +487,12 @@
     )

     @confirm_attributes = [
-      { name: 'type_id',      display: 'Type',     tag: 'select',   multiple: false, null: true, relation: 'TicketArticleType', filter: @articleTypeFilter, default: '9', translate: true, class: 'medium' }
-      { name: 'internal',     display: 'Visibility', tag: 'select', null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '', default: false }
+      { name: 'type_id',  display: 'Type',       tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', filter: @articleTypeFilter, default: '9', translate: true, class: 'medium' }
+      { name: 'internal', display: 'Visibility', tag: 'select', null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '', default: false }
     ]

     new App.ControllerForm(
-      el: @el.find('#form-ticket-bulk-typeVisibility')
+      el: @$('#form-ticket-bulk-typeVisibility')
       model:
         configure_attributes: @confirm_attributes
         className:            'create'
@@ -521,7 +516,7 @@
     # need a delay because of the click event
     setTimeout ( => @$('.textarea.form-group textarea').focus() ), 0

-  reset: =>
+  reset: =>
     @$('.js-action-step').removeClass('hide')
     @$('.js-confirm-step').addClass('hide')

@@ -554,9 +549,9 @@
   submit: (e) =>
     e.preventDefault()

-    @bulk_count = @el.find('.table-overview').find('[name="bulk"]:checked').length
+    @bulk_count = @holder.find('.table-overview').find('[name="bulk"]:checked').length
     @bulk_count_index = 0
-    @el.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
+    @holder.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
       @log 'notice', '@bulk_count_index', @bulk_count, @bulk_count_index
       ticket_id = $(element).val()
       ticket = App.Ticket.find(ticket_id)
@@ -567,8 +562,6 @@
       for item of params
         if params[item] != ''
           ticket_update[item] = params[item]
-
-#      @log 'notice', 'update', params, ticket_update, ticket

       # validate article
       if params['body']
@@ -577,9 +570,9 @@
         params.ticket_id = ticket.id
         params.form_id   = @form_id

-        sender            = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
-        type              = App.TicketArticleType.find( params['type_id'] )
-        params.sender_id  = sender.id
+        sender           = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
+        type             = App.TicketArticleType.find( params['type_id'] )
+        params.sender_id = sender.id

         if !params['internal']
           params['internal'] = false
@@ -606,13 +599,14 @@

           # refresh view after all tickets are proceeded
           if @bulk_count_index == @bulk_count
+            @hide()

             # fetch overview data again
             App.OverviewIndexCollection.fetch()
             App.OverviewCollection.fetch(@view)
       )
     )
-    @el.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
+    @holder.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
     App.Event.trigger 'notify', {
       type: 'success'
       msg: App.i18n.translateContent('Bulk-Action executed!')


don't show loading message on markdown editor if there is no content

--- app/assets/javascripts/builder/views/editor_buttons.js.coffee
+++ app/assets/javascripts/builder/views/editor_buttons.js.coffee
@@ -22,9 +22,10 @@
         new App.Views.MarkdownEditor
           markdownField: '#page_body_md'
           wysiwygField: '#page_body'
-        $('.right.half').find('.content').html """
-
Loading preview. Please wait ...

-          """
+        unless $('.left.half textarea').val() == ''
+          $('.right.half').find('.content').html """
+
Loading preview. Please wait ...

+            """

   launchWysiwygEditor: (e) ->
     e.preventDefault()


Fix container misalignment

Fix container misalignment

--- app/assets/javascripts/components/activity_list.js.jsx.coffee
+++ app/assets/javascripts/components/activity_list.js.jsx.coffee
@@ -17,7 +17,7 @@
     list = _.map activities, (activity) ->
       `<Activity startTracking={startTracking} table={true} key={activity.id} updater={updater} {...activity} />`

-    `

+    `

{title}


wrap draggables in a <div>; only native elements are now draggable

--- app/assets/javascripts/components/high_order/reorderable.cjsx
+++ app/assets/javascripts/components/high_order/reorderable.cjsx
@@ -50,10 +50,14 @@
     render: ->
       if @props.editable
         _.flow(@props.connectDragSource, @props.connectDropTarget)(
-          <Component {...@props} />
+

+            <Component {...@props} />
+

         )
       else
-        <Component {...@props} />
+

+          <Component {...@props} />
+

   )

   # The lodash `flow` function is essentially a chain, passing the return


Animations with velocity.js use the single-object argument syntax, instead of three comma-separated arguments.

See http://julian.com/research/velocity/#arguments

This syntax enables us to specify options like "delay", which is not possible with the three-arguments syntax.

--- app/assets/javascripts/ng-services/ng-AnimationsSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-AnimationsSvc.js.coffee
@@ -36,14 +36,16 @@
       # Set height back to 0px and animate the transition to its final height
       $("#entry-#{entry.id}-summary")
         .css('height', '0')
-        .velocity {height: height_auto, 'padding-top': 15, 'padding-bottom': 15}, 300, 'swing', add_open_class
+        .velocity properties: {height: height_auto, 'padding-top': 15, 'padding-bottom': 15},
+          options: {duration: 300, easing: 'swing', complete: add_open_class}

     #---------------------------------------------
     # Animate closing an entry, by transitioning its height from its current value to zero
     #---------------------------------------------
     close_entry: (entry)->
       $("#entry-#{entry.id}-summary")
-        .velocity {height: 0, 'padding-top': 0, 'padding-bottom': 0}, 300, 'swing', remove_open_class
+        .velocity properties: {height: 0, 'padding-top': 0, 'padding-bottom': 0},
+          options: {duration: 300, easing: 'swing', complete: remove_open_class}

   return service
 ]


ad events (#1881)

ad events (#1881)

--- app/assets/javascripts/revised/components/ad_displayer.coffee
+++ app/assets/javascripts/revised/components/ad_displayer.coffee
@@ -2,22 +2,38 @@
   ads_right = ['right300x600']
   ads_top = ['top468x60']

-  max_tracker_300 = ""
-  max_tracker_468 = ""
+  # Note: links have id of binxad-#{ad name} - which enables click tracking with ga events
+  max_tracker_300 = ""
+  max_tracker_468 = ""

   constructor: ->
+    renderedAds = []
+
     for el_id in ads_right
-      @renderAdRight(el_id) if document.getElementById(el_id)
+      if document.getElementById(el_id)
+        Array::push.apply renderedAds, @renderAdRight(el_id)

     for el_id in ads_top
-      @renderAdTop(el_id) if document.getElementById(el_id)
+      if document.getElementById(el_id)
+        Array::push.apply renderedAds, @renderAdTop(el_id)
+
+    # If google analytics is loaded, create an event for each ad that is loaded, and track the clicks
+    if window.ga
+      for adname in renderedAds
+        window.ga("send", {hitType: "event", eventCategory: "advertisement", eventAction: "ad-load", eventLabel: "#{adname}"})
+        $("#binxad-#{adname}").click (e) ->
+          window.ga("send", {hitType: "event", eventCategory: "advertisement", eventAction: "ad-click", eventLabel: "#{adname}"})

   renderAdRight: (el_id) ->
     $(".content-nav-group:last").addClass("additional-ad-space")
     $("##{el_id}").html([max_tracker_300].join("")).addClass("rendered-ad")

+    ["max_tracker_300"]
+
   renderAdTop: (el_id) ->
     $("##{el_id}").html([max_tracker_468].join("")).addClass("rendered-ad")
+
+    ["max_tracker_468"]

   # geolocatedAd: ->
   #   location = localStorage.getItem('location')


use pinejs request for env vars retrieval

--- src/application.coffee
+++ src/application.coffee
@@ -8,7 +8,7 @@
 utils = require './utils'
 tty = require './lib/tty'
 logger = require './lib/logger'
-{ resinApi, cachedResinApi, request } = require './request'
+{ resinApi, cachedResinApi } = require './request'

 { docker } = dockerUtils

@@ -214,6 +214,21 @@
 	.finally ->
 		updateDeviceState(status: 'Idle')

+getEnvironment = do ->
+	envApiEndpoint = url.resolve(config.apiEndpoint, '/environment')
+
+	return (appId, deviceId, apiKey) ->
+
+		requestParams = _.extend
+			method: 'GET'
+			url: "#{envApiEndpoint}?deviceId=#{deviceId}&appId=#{appId}&apikey=#{apiKey}"
+		, cachedResinApi.passthrough
+
+		cachedResinApi._request(requestParams)
+		.catch (err) ->
+				console.error("Failed to get environment for device #{deviceId}, app #{appId}. #{err}")
+				throw err
+
 # 0 - Idle
 # 1 - Updating
 # 2 - Update required
@@ -252,19 +267,10 @@
 				apikey: apiKey

 		Promise.join deviceId, remoteApps, (deviceId, remoteApps) ->
-			envApiEndpoint = url.resolve(config.apiEndpoint, '/environment')
-
 			return Promise.map remoteApps, (remoteApp) ->
-				request.getAsync
-					url: envApiEndpoint
-					qs:
-						deviceId: deviceId
-						appId: remoteApp.id
-						apikey: apiKey
-				.spread (res, data) ->
-					if res.statusCode >= 400
-						throw new Error("Failed to get environment for device #{uuid}, app #{remoteApp.id}. Status code: #{res.statusCode}")
-					remoteApp.environment_variable = JSON.parse(data)
+				getEnvironment(remoteApp.id, deviceId, apiKey)
+				.then (environment) ->
+					remoteApp.environment_variable = environment
 					return remoteApp
 		.then (remoteApps) ->
 			console.log('Remote apps')


Update the app info the moment we create the container, even if then starting the container fails.

This stops issues with constantly creating new containers for an image that fails to start.

--- src/application.coffee
+++ src/application.coffee
@@ -122,6 +122,14 @@
 					ExposedPorts: ports
 				)
 		.tap (container) ->
+			# Update the app info the moment we create the container, even if then starting the container fails.
+			# This stops issues with constantly creating new containers for an image that fails to start.
+			app.containerId = container.id
+			if app.id?
+				knex('app').update(app).where(id: app.id)
+			else
+				knex('app').insert(app)
+		.tap (container) ->
 			console.log('Starting container:', app.imageId)
 			ports = {}
 			if portList?
@@ -144,13 +152,8 @@
 					es.split()
 					es.mapSync(publish)
 				)
-	.tap (container) ->
+	.tap ->
 		utils.mixpanelTrack('Application start', app.imageId)
-		app.containerId = container.id
-		if app.id?
-			knex('app').update(app).where(id: app.id)
-		else
-			knex('app').insert(app)
 	.finally ->
 		updateDeviceInfo(status: 'Idle')


adds users in realm call

adds users in realm call

--- js/src/http/DirectoryApi.coffee
+++ js/src/http/DirectoryApi.coffee
@@ -21,6 +21,7 @@
   privateKeyUrl      = -> Configuration.get('servicesUrl') + '/directory/private'
   publicKeyUrl       = -> Configuration.get('servicesUrl') + '/directory/public'
   saltUrl            = -> Configuration.get('servicesUrl') + '/directory/salt'
+  usersInRealmUrl    = -> Configuration.get('servicesUrl') + '/directory'

   logger             = Logger.get('DirectoryApi')

@@ -129,4 +130,14 @@
         logger.info('getSalt', { response })
         return new BlockCiphertext(response)

+    # gets users in the specified realm
+    getUsers: ({ realm }) ->
+      Promise.resolve(jquery.ajax(SecurityUtils.wrapRequest({
+        url  : usersInRealmUrl() + '/' + realm
+        type : 'GET'
+      })))
+      .then (users) ->
+        logger.info('getUsers', users)
+        return _.pluck(users, 'name')
+
   return DirectoryApi


Fix wrong logic.

Fix wrong logic.

--- client/Main/providers/machinestatemodal.coffee
+++ client/Main/providers/machinestatemodal.coffee
@@ -159,7 +159,7 @@
     if @state in [ Stopped, NotInitialized, Unknown ]
       @createStateButton()
     else if @state in [ Starting, Building, Pending, Stopping, Terminating, Updating, Rebooting ]
-      {percentage} = response?
+      percentage = response?.percentage
       @createProgressBar percentage
       @triggerEventTimer percentage
     else if @state is Terminated


MachineStateModal: split updateStatus logic into small bits

--- client/Main/providers/machinestatemodal.coffee
+++ client/Main/providers/machinestatemodal.coffee
@@ -67,53 +67,58 @@
     {status, percentage, error} = event

     if status is @state
-
-      if percentage?
-
-        @triggerEventTimer percentage
-
-        @progressBar?.updateBar Math.max percentage, 10
-        @progressBar?.show()
+      @updatePercentage percentage  if percentage?

     else
-
-      @state = status
+      @state    = status
       @hasError = error?.length > 0

-      if percentage?
-
-        if percentage is 100
-
-          @clearEventTimer()
-
-          if status is Running
-            @prepareIDE()
-            @destroy()
-
-          else
-            @progressBar?.updateBar 100
-            @progressBar?.show()
-
-            KD.utils.wait 500, => @buildViews()
-
-        else if task is 'reinit'
-
-          @progressBar?.updateBar Math.max percentage, 10
-          @progressBar?.show()
-          @label?.updatePartial @getStateLabel()
-
-          @triggerEventTimer percentage
-
-        else
-
-          @clearEventTimer()
-          @buildViews()
+      unless percentage?
+        return @switchToIDEIfNeeded()
+
+      if percentage is 100
+        @completeCurrentProcess status
+
+      else if task is 'reinit'
+        @updatePercentage percentage
+        @updateReinitState()

       else
-
-        if status is Running
-          @prepareIDE()
-          @destroy()
+        @clearEventTimer()
+        @buildViews()
+
+
+  switchToIDEIfNeeded: (status = @state)->
+
+    return no  unless status is Running
+    @prepareIDE()
+    @destroy()
+    return yes
+
+
+  updatePercentage: (percentage)->
+
+    @triggerEventTimer percentage
+
+    @progressBar?.updateBar Math.max percentage, 10
+    @progressBar?.show()
+
+
+  updateReinitState: ->
+
+    @label?.updatePartial @getStateLabel()
+
+
+  completeCurrentProcess: (status)->
+
+    @clearEventTimer()
+
+    return  if @switchToIDEIfNeeded status
+
+    @progressBar?.updateBar 100
+    @progressBar?.show()
+
+    KD.utils.wait 500, => @buildViews()

   buildInitial:->


MainTabView: Fixed router.back issue on main tabview.

--- client/app/MainApp/maintabs/maintabview.coffee
+++ client/app/MainApp/maintabs/maintabview.coffee
@@ -78,8 +78,15 @@
     # to show the previousPane when a pane
     # is removed, that's why we override it to use
     # kodingrouter
+
+    index        = @getPaneIndex pane
+    visibles     = @getVisibleTabs()
+    visibleIndex = visibles.indexOf pane
+    leftPane     = visibles[visibleIndex - 1]
+    rightPane    = visibles[visibleIndex + 1]
+
     pane.emit "KDTabPaneDestroy"
-    index = @getPaneIndex pane
+
     isActivePane = @getActivePane() is pane
     @panes.splice index, 1
     pane.destroy()
@@ -87,13 +94,13 @@
     @handles.splice index, 1
     handle.destroy()
     @emit "PaneRemoved"
-    if isActivePane
-      if prevPane = @getPaneByIndex @lastOpenPaneIndex
-        appInstance = @appManager.getByView prevPane.mainView
-        @appManager.showInstance appInstance
-      else
-        @router.back()

+    if rightPane
+      @appManager.showInstance @appManager.getByView rightPane.mainView
+    else if leftPane
+      @appManager.showInstance @appManager.getByView leftPane.mainView
+    else
+      @router.handleRoute "/Activity"

   createTabPane:(options = {}, mainView)->


Added editor default settings map.

Added editor default settings map.

--- client/ide/workspace/panes/settingspane.coffee
+++ client/ide/workspace/panes/settingspane.coffee
@@ -93,6 +93,20 @@
       'softWrap', 'keyboardHandler', 'scrollPastEnd', 'openRecentFiles'
     ]

+  defaults:
+    useSoftTabs          : yes
+    showGutter           : yes
+    highlightActiveLine  : yes
+    scrollPastEnd        : yes
+    openRecentFiles      : yes
+    showInvisibles       : no
+    useWordWrap          : no
+    showPrintMargin      : no
+    fontSize             : 12
+    tabSize              : 4
+    softWrap             : 'off'
+    keyboardHandler      : 'default'
+
   pistachio: ->
     """

Editor Settings


[Email Confirmation] move clear back to where it is

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -315,7 +315,7 @@
           else
             new KDNotificationView
               title: "Thanks for confirming your email address!"
-            @clear()
+          @clear()

       '/member/:username': ({params:{username}})->
         @handleRoute "/#{username}", replaceState: yes


Label

Label

- Fixed potential memory leak.

--- src/elements/Label/Label.coffee
+++ src/elements/Label/Label.coffee
@@ -140,7 +140,7 @@
 			@__overflow_button_div = $div("cui-label-manage-overflow-button-container", style: "display: none;")

 			if @_manage_overflow == true
-				btn_opts = CUI.defaults.class.Label.defaults.manage_overflow
+				btn_opts = copyObject(CUI.defaults.class.Label.defaults.manage_overflow, true)
 			else
 				btn_opts = @_manage_overflow


reorganizing/cleaning up stuff

reorganizing/cleaning up stuff

--- resources/assets/coffee/_classes/store-supporter-tag.coffee
+++ resources/assets/coffee/_classes/store-supporter-tag.coffee
@@ -28,7 +28,6 @@
     @debouncedGetUser = _.debounce @getUser, 300
     @el = rootElement
     @searching = false
-    @searchData = null

     # Everything should be scoped under the root @el
     @priceElement = @el.querySelector('.js-price')
@@ -43,12 +42,13 @@
       username: @el.dataset.username
       avatarUrl: @el.dataset.avatarUrl

-    @initializeSlider()
+    @cost = @calculate(@initializeSlider().slider('value'))
     @initializeSliderPresets()
     @initializeUsernameInput()
+    @updateCostDisplay()

   initializeSlider: =>
-    slider = $(@slider).slider
+    $(@slider).slider
       range: 'min'
       value: @sliderValue(@MIN_VALUE)
       min: @sliderValue(@MIN_VALUE)
@@ -57,9 +57,6 @@
       animate: true
       slide: @onSliderValueChanged
       change: @onSliderValueChanged
-
-    @updatePrice(@calculate(@sliderValue(@MIN_VALUE)))
-    slider

   initializeSliderPresets: =>
     $(@sliderPresets).on 'click', (event) =>
@@ -91,9 +88,8 @@
     new StoreSupporterTagPrice(Math.floor(position / @RESOLUTION))

   onSliderValueChanged: (event, ui) =>
-    values = @calculate(ui.value)
-    @updatePrice(values)
-    @updateSliderPresets(values)
+    @cost = @calculate(ui.value)
+    @updateCostDisplay()

   onInput: (event) =>
     if !@searching
@@ -130,15 +126,13 @@
     )
     @setUserInteraction(@user?)

-  updatePrice: (obj) =>
-    @el.querySelector('input[name="item[cost]"').value = obj.price()
-    @el.querySelector('input[name="item[extra_data][duration]"').value = obj.duration()
-    @priceElement.textContent = "USD #{obj.price()}"
-    @durationElement.textContent = obj.durationText()
-    @discountElement.textContent = "save #{obj.discount()}%"
+  updateCostDisplay: =>
+    @el.querySelector('input[name="item[cost]"').value = @cost.price()
+    @el.querySelector('input[name="item[extra_data][duration]"').value = @cost.duration()
+    @priceElement.textContent = "USD #{@cost.price()}"
+    @durationElement.textContent = @cost.durationText()
+    @discountElement.textContent = "save #{@cost.discount()}%"
+    @updateSliderPreset(elem, @cost) for elem in @sliderPresets

-  updateSliderPresets: (values) =>
-    @updateSliderPreset(elem, values) for elem in @sliderPresets
-
-  updateSliderPreset: (elem, values) ->
-    $(elem).toggleClass('store-slider__preset--active', values.duration() >= +elem.dataset.months)
+  updateSliderPreset: (elem, cost) ->
+    $(elem).toggleClass('store-slider__preset--active', cost.duration() >= +elem.dataset.months)


Fix mysqlDB Api

Fix mysqlDB Api

--- kites/databases/mySQLApi.coffee
+++ kites/databases/mySQLApi.coffee
@@ -130,6 +130,9 @@

     {username,dbUser,dbName} = options

+    dbUser ?= uniqueId()
+    dbName ?= dbUser
+
     dbUser = escape dbUser
     dbName = escape dbName

@@ -138,12 +141,15 @@
     #
     dbName = appendKodingUsername username,dbName
     dbUser = appendKodingUsername username,dbUser
-    #
+
     # we only create aleksey_myDbName kind of databases (dot is not safe to use)
     # so we can count how many databases this user already has.
     # if user is granted permission to another database
     # we know how many he owns, how many he can access separately.
     # -------------------------------
+
+    options.dbName = dbName
+    options.dbUser = dbUser

     sendResult = (err,result)=>
       result.dbType = "mysql"


fetches work

fetches work

--- desktop/apps/editorial_features/routes.coffee
+++ desktop/apps/editorial_features/routes.coffee
@@ -44,18 +44,14 @@
       res.locals.sd.CURATION = curation.toJSON()
       videoIndex = 0
       if @curation.get('sub_articles').length
-        @sub_articles = curation.get('sub_articles')
-        # cbs = [ console.log 'in a callback' ]
-        console.log @sub_articles
         cbs = [
           @veniceSubArticles.fetch
-            data: ids: @sub_articles
+            data: 'ids[]': @curation.get('sub_articles')
         ]
       else
-        cbs = [ console.log 'in empty callback' ]
+        cbs = [ () -> ]
       Q.all(cbs)
-      .then =>
-        console.log @veniceSubArticles
+      .then (err, results) =>
         if req.params.slug
           videoIndex = setVideoIndex(curation, req.params.slug)
           unless videoIndex or videoIndex is 0
@@ -64,8 +60,7 @@
         res.render 'components/venice_2017/templates/index',
           videoIndex: videoIndex
           curation: curation
-          sub_articles: []
-          # sub_articles: @veniceSubArticles?.toJSON()
+          sub_articles: @veniceSubArticles?.toJSON()
     error: next

 @vanity = (req, res, next) ->


Add section for resolve issues

Add section for resolve issues

--- admin/static/coffee/resolve_issues.coffee
+++ admin/static/coffee/resolve_issues.coffee
@@ -2,7 +2,7 @@
 module 'ResolveIssuesView', ->
     # ResolveIssuesView.Container
     class @Container extends Backbone.View
-        className: 'resolve-issues'
+        className: 'resolve-issues section'
         template_outer: Handlebars.compile $('#resolve_issues-container-outer-template').html()
         template_inner: Handlebars.compile $('#resolve_issues-container-inner-template').html()


Prism now applied to the Jetpack InfinityScroll-loaded posts also

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -1,2 +1,14 @@
 jQuery ($) ->
-    $+
+    if typeof Prism isnt 'undefined'
+
+        # ----------------------------------------------------------------------------------------------------
+        #
+        # Bind 'post-load' event (from Jetpack infinityScroll) to apply Prism magic on code blocks
+        #
+        $('body').on 'post-load', ->
+            # We only apply Prism on the last n posts, to save resource
+            $('.post')
+                .slice(0 - window.infiniteScroll.settings.query_args.posts_per_page).find('code[class^=lang]')
+                .each ->
+                    Prism.highlightElement @


Optimist boolean() doesn't have 2nd parameter

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -245,13 +245,14 @@
             .describe("nocolor", "Don't colorize the output")
             .describe("s", "Lint the source from stdin")
             .describe("q", "Only print errors.")
+            .describe("literate",
+                "Used with --stdin to process as Literate CoffeeScript")
             .boolean("csv")
             .boolean("jslint")
             .boolean("nocolor")
             .boolean("noconfig")
             .boolean("makeconfig")
-            .boolean("literate",
-                "Used with --stdin to process as Literate CoffeeScript")
+            .boolean("literate")
             .boolean("r")
             .boolean("s")
             .boolean("q", "Print errors only.")


Improved fetch to support arguments

Improved fetch to support arguments

--- core/app/backbone/collections/followers.coffee
+++ core/app/backbone/collections/followers.coffee
@@ -22,9 +22,13 @@
     @totalPages = Math.floor(response.total / @perPage)
     response.users

-  fetch: ->
-    super success: =>
-      @trigger 'change'
+  fetch: (options={})->
+    success = options.success
+    new_success = (args...) =>
+        @trigger 'change'
+        success(args...) if success?
+
+    super _.extend({ success: new_success }, options)

   total_count: ->
     @totalRecords


activity: render channel thread pane with immutable render mixin

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -1,14 +1,17 @@
-kd                = require 'kd'
-React             = require 'kd-react'
-KDReactorMixin    = require 'app/flux/reactormixin'
-ActivityFlux      = require 'activity/flux'
-immutable         = require 'immutable'
-classnames        = require 'classnames'
-ThreadSidebar     = require 'activity/components/threadsidebar'
-ThreadHeader      = require 'activity/components/threadheader'
-PublicChannelLink = require 'activity/components/publicchannellink'
+kd                   = require 'kd'
+React                = require 'kd-react'
+KDReactorMixin       = require 'app/flux/reactormixin'
+ActivityFlux         = require 'activity/flux'
+immutable            = require 'immutable'
+classnames           = require 'classnames'
+ThreadSidebar        = require 'activity/components/threadsidebar'
+ThreadHeader         = require 'activity/components/threadheader'
+PublicChannelLink    = require 'activity/components/publicchannellink'
+ImmutableRenderMixin = require 'react-immutable-render-mixin'

 module.exports = class ChannelThreadPane extends React.Component
+
+  @include [ ImmutableRenderMixin ]

   { getters } = ActivityFlux


Enable autocomplete setting for Ace editor

--- client/ide/workspace/panes/settings/editorsettingsview.coffee
+++ client/ide/workspace/panes/settings/editorsettingsview.coffee
@@ -38,6 +38,10 @@
       cssClass           : "tiny settings-on-off"
       callback           : (state) => @emit 'SettingsChanged', 'scrollPastEnd', state

+    @enableAutocomplete       = new KodingSwitch
+      cssClass           : "tiny settings-on-off"
+      callback           : (state) => @emit 'SettingsChanged', 'enableAutocomplete', state
+
     @openRecentFiles     = new KodingSwitch
       cssClass           : "tiny settings-on-off"
       callback           : (state) => @emit 'SettingsChanged', 'openRecentFiles', state
@@ -77,6 +81,7 @@
       'theme', 'useSoftTabs', 'showGutter', 'useWordWrap', 'showPrintMargin'
       'highlightActiveLine', 'showInvisibles', 'fontSize', 'tabSize'
       'keyboardHandler', 'scrollPastEnd', 'openRecentFiles', 'useAutosave'
+      'enableAutocomplete'
     ]

@@ -86,6 +91,7 @@
     showGutter           : yes
     highlightActiveLine  : yes
     scrollPastEnd        : yes
+    enableAutocomplete   : yes
     openRecentFiles      : yes
     showInvisibles       : no
     useWordWrap          : no
@@ -107,6 +113,7 @@

Highlight active line           {{> @highlightActiveLine}}

Show invisibles                 {{> @showInvisibles}}

Use scroll past end             {{> @scrollPastEnd}}

+
Enable autocomplete             {{> @enableAutocomplete}}

Key binding {{> @keyboardHandler}}

Font size   {{> @fontSize}}

Theme       {{> @theme}}


Faye interface turned into mixin.

Faye interface turned into mixin.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -104,7 +104,7 @@

 #----------------------------------- faye collection

-class FayeCollection extends Backbone.Collection
+RemoteObjectMixin = {

         listen: (path) ->
                 @faye_path = path
@@ -166,6 +166,13 @@
                 @full_feed.cancel()
                 @incremental_feed.cancel()
                 window.clearInterval(@refetch_timer)
+        }
+
+
+class RemoteCollection extends Backbone.Collection
+        constructor: (params...)->
+                super params...
+                $.extend(@, RemoteObjectMixin)

 #----------------------------------- controllers

@@ -174,7 +181,7 @@
         initialize: (options) ->
                 @api = options.api
                 @listenTo(@api, "navigate", @navigate)
-                @reports = new FayeCollection
+                @reports = new RemoteCollection
                 @reports.listen("/reports")
                 @reportListView = new ReportListView
                 @reportListView.collection = @reports
@@ -207,7 +214,7 @@
                                 @reportView.close()
                         if new_state.report and new_state.report.length > 0
                                 $(".report_row").removeClass("focused_report")
-                                @report = new FayeCollection
+                                @report = new RemoteCollection
                                 @report.listen("/report/"+new_state.report)
                                 @reportDescription = new ReportDescription({data: @report})
                                 @reportView = new ReportView(model: @reportDescription)


Normalize encoding name in status view

Normalize encoding name in status view

--- lib/encoding-status-view.coffee
+++ lib/encoding-status-view.coffee
@@ -44,6 +44,7 @@
   updateEncodingText: ->
     encoding = @getActiveTextEditor()?.getEncoding()
     if encoding?
+      encoding = encoding.toLowerCase().replace(/[^0-9a-z]|:\d{4}$/g, '')
       @encodingLink.textContent = Encodings[encoding]?.status ? encoding
       @encodingLink.dataset.encoding = encoding
       @style.display = ''


Improve the error message for missing plugins.

--- lib/finds-plugin-modules.coffee
+++ lib/finds-plugin-modules.coffee
@@ -3,6 +3,8 @@
 resolvesQuietly = require('./../lib/resolves-quietly')
 semver = require('semver')
 fs = require('fs')
+path = require('path')
+grunt = require('./requires-grunt').require()

 module.exports =
   find: ->
@@ -19,16 +21,24 @@
     .value()

 packageDependencies = (dir) ->
-  packageJson = require("#{dir}/package")
+  packageJson = require(path.join(dir, "package"))
   _({}).extend(packageJson.optionalDependencies, packageJson.devDependencies, packageJson.dependencies)

 isLinemanPlugin = (name) -> _str.startsWith(name, "lineman-")

-pluginObjectBuilder = (dir, depth) ->
+pluginObjectBuilder = (basedir, depth) ->
   (name) ->
-    dir = resolvesQuietly.resolve(name, basedir: dir)
-    version = require("#{dir}/package").version
-    {name, version, dir, depth}
+    if dir = resolvesQuietly.resolve(name, basedir: basedir)
+      version = require(path.join(dir, "package")).version
+      {name, version, dir, depth}
+    else
+      grunt.warn """
+                 Could not find plugin "#{name}", even though it was listed in your "package.json" file.
+
+                 You probably need to run `npm install #{name}`
+
+                 (Performed search from "#{basedir}")
+                 """

 descendantPluginCollector = (depth, knownPlugins) ->
   (deps, dep) ->


Fix: panto on map when user mouse over the event

--- app/assets/javascripts/newsfeed.js.coffee
+++ app/assets/javascripts/newsfeed.js.coffee
@@ -68,6 +68,7 @@
         # mostra o a distância de ônibus
         $(".js-distanceWithCar .data").fadeIn("fast").text($(this).attr("car"))

+        # TODO: Use Gmap module to construct this map
         Gmaps.panTo($(this).attr("latitude"), $(this).attr("longitude"))

         return


remove gruntfile from branch

remove gruntfile from branch

--- Frontend/Gruntfile.coffee
+++ Frontend/Gruntfile.coffee
@@ -1,84 +0,0 @@
-module.exports = (grunt) ->
-
-  # Initial definitions for folders
-  OUTPUT_FOLDER = "C:/Users/rieb/Downloads/apache-tomcat-8.5.5-windows-x64/apache-tomcat-8.5.5/webapps/ROOT"
-
-  grunt.initConfig
-
-    copy:
-      static:
-        options:
-          flatten: true
-        files: [
-          # CSS Content
-          {cwd: "./src/static/css", src: '**', dest: "#{OUTPUT_FOLDER}/css", expand: true}
-          # HTML Content
-          {cwd: "./src/static/html", src: '**', dest: "#{OUTPUT_FOLDER}", expand: true}
-          # Image content
-          {cwd: "./src/static/images", src: '**', dest: "#{OUTPUT_FOLDER}/img", expand: true}
-          # Font content
-          {cwd: "./src/static/fonts", src: '**', dest: "#{OUTPUT_FOLDER}/fonts", expand: true}
-          # JS content
-          {cwd: "./node_modules/json-formatter-js/dist", src: 'bundle.js', dest: "#{OUTPUT_FOLDER}/js", expand: true}
-        ]
-
-      training:
-        options:
-          flatten: true
-        files: [
-          {src: ['./build/tmp/compileBrowserify/dist.js'], dest: "#{OUTPUT_FOLDER}/js/training.js"}
-        ]
-
-    clean:
-      browserify: ['./build/tmp/compileBrowserify']
-      out: [OUTPUT_FOLDER]
-      options:
-        force: true
-
-    browserify:
-      code:
-        options:
-          debug: true
-          transform: [
-            [
-              'babelify',
-              {
-                presets: ["es2015"]
-                plugins: ["transform-react-jsx", "transform-object-rest-spread"]
-                global: true,
-                babelrc: false
-              }
-            ]
-          ]
-        dest: "#{OUTPUT_FOLDER}/js/training.js"
-        src: ['src/main/**/*.js']
-      watch:
-        options:
-          debug: true
-          transform: [
-            [
-              'babelify',
-              {
-                presets: ["es2015"]
-                plugins: ["transform-react-jsx", "transform-object-rest-spread"]
-                global: true,
-                babelrc: false
-              }
-            ]
-          ]
-          watch: true
-          watchifyOptions:
-            poll: true
-          keepAlive: true
-        dest: "#{OUTPUT_FOLDER}/js/training.js"
-        src: ['src/main/**/*.js']
-
-
-
-  # Babel Conversion
-  grunt.loadNpmTasks 'grunt-browserify'
-  grunt.registerTask 'build-frontend', ['browserify:code']
-  grunt.registerTask 'release-frontend', ['clean:out', 'build-frontend', 'copy:static']
-
-  grunt.loadNpmTasks 'grunt-contrib-clean'
-  grunt.loadNpmTasks 'grunt-contrib-copy'


Remove obvious.

Remove obvious.

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -55,7 +55,6 @@
                 ref: 'lyrics'
                 value: song.lyrics
               p className: 'help-block',
-                editMode && 'Just edit song, no need to save it. '
                 a
                   href: 'http://linkesoft.com/songbook/chordproformat.html'
                   target: '_blank'


Smarter text with html chords pasting.

Smarter text with html chords pasting.

Place over selected text or at cursor position. Enforce cursor position
after pasting.

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -3,6 +3,7 @@
 goog.require 'app.songs.Song'
 goog.require 'goog.array'
 goog.require 'goog.dom'
+goog.require 'goog.dom.selection'
 goog.require 'goog.labs.userAgent.browser'
 goog.require 'goog.labs.userAgent.device'
 goog.require 'goog.ui.Textarea'
@@ -168,6 +169,7 @@
         @chordproTextarea_.dispose()

       onFieldChange: (e) ->
+        console.log 'changed: ' + e.target.name
         userStore.updateSong song, e.target.name, e.target.value

       onFormSubmit: (e) ->
@@ -197,6 +199,7 @@
         if !userStore.isLogged()
           alert EditSong.MSG_LOGIN_TO_PUBLISH
           return
+        # TODO: Move to XHR itself.
         return if !online.check()
         songsStore
           .publish song
@@ -214,29 +217,26 @@
         # TODO: Check IE11, IE12, IE13...
         return if goog.labs.userAgent.browser.isIE()

-        try
-          # Returns empty string for data without HTML.
-          html = e.clipboardData.getData 'text/html'
-        catch e
-          # TODO: Log to server.
-
-        if html
-          # Prevent pasting plain text when we have rich HTML.
-          e.preventDefault()
-          previousLyrics = e.target.value
-          lyrics = if previousLyrics.trim()
-            if goog.string.endsWith previousLyrics, '\n'
-              previousLyrics
-            else
-              previousLyrics + '\n'
-          else
-            ''
-          # Append because app model doesn't define caret position.
-          # Can I read caret position?
-          lyrics += @parsePastedHtml html
-          userStore.updateSong song, 'lyrics', lyrics
-
-      parsePastedHtml: (html) ->
+        # Can be empty string for data without any HTML.
+        html = e.clipboardData.getData 'text/html'
+        # Do nothing aka let browser to paste plain text.
+        return if !html
+
+        e.preventDefault()
+        text = @convertPastedHtmlToText html
+        target = e.target
+        endPoints = goog.dom.selection.getEndPoints target
+        before = target.value.substr 0, endPoints[0]
+        after = target.value.substr endPoints[1]
+        lyrics = before + text + after
+        userStore.updateSong song, 'lyrics', lyrics
+        # Give a React time to update, setCursorPosition is not destructive so
+        # it's ok to use timeout.
+        setTimeout ->
+          goog.dom.selection.setCursorPosition target, (before + text).length
+        , 10
+
+      convertPastedHtmlToText: (html) ->
         node = goog.dom.htmlToDocumentFragment html
         # Convert sup elements to chords. Used by supermusic.sk for example.
         sups = node.querySelectorAll 'sup'


Fix intendation error

Fix intendation error

--- client/app.coffee
+++ client/app.coffee
@@ -66,11 +66,12 @@
       crs.project(rectangle_editor.getMarkerBounds()[0].getNorthWest()),
       crs.project(rectangle_editor.getMarkerBounds()[0].getSouthEast())
     )
-    rectangle_editor.on 'change', (event) ->
-      syncTerrainWithSelector()
-      if localStorage
-        localStorage.setItem('rectangle', JSON.stringify(event.bounds))
-        localStorage.setItem('zoom', map.getZoom())
+
+  rectangle_editor.on 'change', (event) ->
+    syncTerrainWithSelector()
+    if localStorage
+      localStorage.setItem('rectangle', JSON.stringify(event.bounds))
+      localStorage.setItem('zoom', map.getZoom())

   canvas = $('canvas#terrain')[0]
   if false && canvas?


Remove unused require

Remove unused require

--- src/project.coffee
+++ src/project.coffee
@@ -5,7 +5,6 @@
 fs = require 'fs-plus'
 Q = require 'q'
 {includeDeprecatedAPIs, deprecate} = require 'grim'
-{Subscriber} = require 'emissary'
 {Emitter} = require 'event-kit'
 Serializable = require 'serializable'
 TextBuffer = require 'text-buffer'


customscroll: document keydown event unbinding, refactoring

--- lib/components/scrollview/customscrollviewinner.coffee
+++ lib/components/scrollview/customscrollviewinner.coffee
@@ -20,11 +20,11 @@
     options.attributes ?= {}
     options.attributes.tabindex ?= "0"

-    @documentKeydownHandled = no
+    @globalKeydownEventBound = no

     super options, data

-    @on 'MutationHappened', @bound "handleDocumentKeydown"
+    @on 'MutationHappened', @bound "toggleGlobalKeydownEventOnSizeCheck"

     return unless KD.utils.isTouchDevice()

@@ -122,19 +122,28 @@
       return shouldStop

-  handleDocumentKeydown: ->
-
-    return  if @documentKeydownHandled
+  toggleGlobalKeydownEventOnSizeCheck: ->

     winHeight = $(window).height()
-    if @getHeight() >= winHeight
-      @documentKeydownHandled = yes
-      $(document).on "keydown.customscroll#{@getId()}", @bound "keyDown"
+    needToBind = @getHeight() >= winHeight
+    @toggleGlobalKeydownEvent needToBind
+
+
+  toggleGlobalKeydownEvent: (needToBind) ->
+
+    eventName = "keydown.customscroll#{@getId()}"
+
+    if needToBind
+      $(document).on eventName, @bound "keyDown"  unless @globalKeydownEventBound
+    else
+      $(document).off eventName  if @globalKeydownEventBound
+
+    @globalKeydownEventBound = needToBind

   destroy: ->

-    $(document).off "keydown.customscroll#{@getId()}"
+    @toggleGlobalKeydownEvent no
     super


user @timezone instead of @alarmTimezone

--- app/controllers/alarms_controller.coffee
+++ app/controllers/alarms_controller.coffee
@@ -63,7 +63,7 @@
     send @alarm, 200

 action 'create', ->
-    triggerDate = new time.Date(req.body.trigg, @alarmTimezone)
+    triggerDate = new time.Date(req.body.trigg, @timezone)
     triggerDate.setTimezone('UTC')
     req.body.trigg = triggerDate.toString().slice(0, 24)


Oops, missed a GitBridge.conflictsIn reference.

--- lib/merge-conflicts-view.coffee
+++ lib/merge-conflicts-view.coffee
@@ -87,8 +87,7 @@
     return unless atom.project.getRepo()
     return if @instance?

-    root = atom.project.getRootDirectory().getRealPathSync()
-    GitBridge.conflictsIn root, (conflicts) =>
+    GitBridge.withConflicts (conflicts) =>
       if conflicts.length > 0
         view = new MergeConflictsView(conflicts)
         @instance = view


:fire: Debug code

:fire: Debug code

--- lib/mixins/canvas-drawer.coffee
+++ lib/mixins/canvas-drawer.coffee
@@ -451,8 +451,6 @@
             )

         intactRange = newIntactRanges[newIntactRanges.length - 1]
-        if intactRange? and (isNaN(intactRange.end) or isNaN(intactRange.start))
-          debugger

       intactRanges = newIntactRanges


修正router.redirectPath不可用的BUG

修正router.redirectPath不可用的BUG

--- src/core/router.coffee
+++ src/core/router.coffee
@@ -305,9 +305,9 @@
 			router = _findRouter(path)
 			if router then _switchRouter(router, path)
 		else
-			path = cola.setting("defaultRouterPath")
-			router = _findRouter(path)
-			if router then cola.setRoutePath(path + location.search, true, true)
+			router = _findRouter(null)
+			if router
+				cola.setRoutePath((router.redirectTo or cola.setting("defaultRouterPath") or "") + location.search, true, true)
 		return
 	, 0)
 	return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -144,6 +144,7 @@
     include: (modules...) ->
       for Module in modules
         if Module.isString?()
+          moduleName =
           Module = Module.constantize()

         else


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -147,10 +147,10 @@
           moduleName = Module
           Module = Module.constantize()
         else
-          # Attempt to discover module name
+

         unless I.included_modules.include moduleName
-          I.included_modules.push module
+          I.included_modules.push moduleName
           self.extend Module(I, self)

       return self


Don't let users put code outside their function by having weirdly matched braces.

--- src/languages/javascript.coffee
+++ src/languages/javascript.coffee
@@ -165,6 +165,9 @@
   parse: (code, aether) ->
     # loc: https://github.com/codecombat/aether/issues/71
     ast = esprima.parse code, {range: true, loc: true}
+
+    if ast? and ast.body.length isnt 1
+      throw new SyntaxError('You seem to have code outside your function. Are your { and } braces matched?');
     ast

   # Optional: if parseDammit() is implemented, then if parse() throws an error, we'll try again using parseDammit().


OX-299 key Info added

OX-299 key Info added

--- app/assets/javascripts/oxalis/view.coffee
+++ app/assets/javascripts/oxalis/view.coffee
@@ -270,8 +270,13 @@

P	Previous comment	C	Create new tree

N	Next comment

T	Toggle theme

+
M	Toggle mode

3D-view

-
Mousewheel	Zoom in and out

+
Leftclick or Arrow keys	Rotation

+
W A S D	Strafe

+
Space, Shift + Space	Forward, Backward

+
K, L	Scale viewports

+
I, O	Zoom in and out


Fix bad jquery selector for signup animation

--- app/assets/javascripts/register.js.coffee
+++ app/assets/javascripts/register.js.coffee
@@ -1,5 +1,5 @@
 $ ->
-  $('.devise-registrations-new .user_email input').blur ->
+  $('.registrations .user_email input').blur ->
     $input = $(@)
     $sublogo = $input.parents('.login').find('.sub-logo')
     email = $input.val()


Get attachment from the text's collection

--- src/trix/controllers/text_controller.coffee
+++ src/trix/controllers/text_controller.coffee
@@ -33,7 +33,7 @@
   # Attachment controller management

   installAttachmentController: (element) ->
-    attachment = Trix.Attachment.get(element.trixAttachmentId)
+    attachment = @text.attachments.get(element.trixAttachmentId)
     unless @attachmentController?.attachment is attachment
       @uninstallAttachmentController()
       @attachmentController = Trix.AttachmentController.create(attachment, element, @element)


Add progress event

Add progress event

--- src/request.coffee
+++ src/request.coffee
@@ -15,7 +15,7 @@
     @_emitter = new Emitter
     @xhr = new XMLHttpRequest

-    events = ["before", "success", "error", "complete"]
+    events = ["before", "success", "error", "complete", "progress"]
     for event in events
       if typeof options[event] is "function"
         @_emitter.on event, options[event]
@@ -25,7 +25,8 @@
     @merge this, options

   send: ->
-    @xhr.addEventListener "readystatechange", @_handleStateChange.bind(this)
+    @xhr.addEventListener "progress", @_progressChange.bind(this)
+    @xhr.addEventListener "readystatechange", @_stateChange.bind(this)
     @xhr.open(@method, @url, @async, @username, @password)

     @_setRequestHeaders()
@@ -49,7 +50,10 @@
     if @_dataIsObject()
       @xhr.setRequestHeader "Content-Type", "application/json;charset=UTF-8"

-  _handleStateChange: ->
+  _progressChange: (args...) ->
+    @_emitter.emit "progress", args...
+
+  _stateChange: ->
     return unless @xhr.readyState is XMLHttpRequest.DONE

     if @xhr.status in [200..299]


model_postgres: optional Transaction callbacks

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -158,7 +158,7 @@
             process.nextTick =>
                 dbIsAvailable @db

-            cb err
+            cb? err

     rollback: (cb) ->
         @db.query "ROLLBACK", [], (err, res) =>
@@ -166,7 +166,7 @@
             process.nextTick =>
                 dbIsAvailable @db

-            cb err
+            cb? err

     ##
     # Actual data model


Doc reporter formating refactored. Issue #3

--- cakes.coffee
+++ cakes.coffee
@@ -98,14 +98,6 @@
 			mocha.describe '', ->
 				mocha.describe message, callback

-if SPEC_REPORTER == 'doc'  # Don't display nest labels for doc output.
-	describeItNest = (command, message, callback, options)->
-		mocha.describe '', ->
-			if command == 'it'
-				mocha.it message, callback
-			else
-				mocha.describe message, callback
-
 nestLabel = (options)->  # returns label of nested describes/its
 	label = ''
 	if 'label' in options
@@ -115,6 +107,10 @@
 				label = label.black
 			else
 				label = label.green
+
+	if SPEC_REPORTER == 'doc'
+		label = ''
+
 	return label

 isPending = (command, message, cb)->  # Return Pending message.


state.same, prop.same and prop.onChange

state.same, prop.same and prop.onChange

--- components.coffee
+++ components.coffee
@@ -25,11 +25,15 @@
 J.Reactive = (params) -> new ReactiveDef params

 J._defineComponent = (componentName, componentSpec) ->
-#    for memberName in [
-#        'getDefaultProps', 'getInitialState', 'shouldComponentUpdate', 'componentWillUpdate'
-#    ]
-#        if memberName of componentSpec
-#            throw "Unnecessary to define #{memberName} for J Framework components."
+    for memberName in [
+        'getDefaultProps'
+        'getInitialState'
+        'componentWillReceiveProps'
+        'shouldComponentUpdate'
+        'componentWillUpdate'
+    ]
+        if memberName of componentSpec
+            throw "Unnecessary to define #{memberName} for J Framework components."

     reactSpec = _.clone componentSpec
@@ -146,7 +150,8 @@
         propSpecs.children =
             type: React.PropTypes.arrayOf React.PropTypes.element
         for propName, propSpec of propSpecs
-            @_props[propName] = new ReactiveVar @props[propName], J.util.equals
+            @_props[propName] = new ReactiveVar @props[propName],
+                propSpec.same ? J.util.equals
             @prop[propName] = do (propName) => =>
                 @_props[propName].get()

@@ -176,7 +181,8 @@
                     stateFieldSpec.default.apply @
                 else
                     stateFieldSpec.default
-            initialState[stateFieldName] = new ReactiveVar initialValue, J.util.equals
+            initialState[stateFieldName] = new ReactiveVar initialValue,
+                stateFieldSpec.same ? J.util.equals

         initialState

@@ -198,7 +204,19 @@
         componentSpec.componentWillReceiveProps?.call @, nextProps

         for propName, newValue of nextProps
+            propSpec =
+                if propName in ['className', 'children']
+                    {}
+                else
+                    componentSpec.props[propName]
+
+            equalsFunc = propSpec.same ? J.util.equals
+            oldValue = Tracker.nonreactive => @_props[propName].get()
+
             @_props[propName].set newValue
+
+            unless equalsFunc oldValue, newValue
+                propSpec.onChange?.call @, oldValue, newValue

     reactSpec.componentDidMount = ->


More specific about when to turn off compupdate.

--- client/controls.coffee
+++ client/controls.coffee
@@ -161,7 +161,7 @@
       id: 'compupdate'
       label: 'Comp update'
       checked: true
-      popover: "Perform compression analysis if the table is empty. Turn off if you don't care about compression."
+      popover: "Perform compression analysis if the table is empty. Turn this off if you're frequently copying large data sets into an empty table."
     }
     {
       id: 'statupdate'


check port range

check port range

--- src/dht-network.coffee
+++ src/dht-network.coffee
@@ -34,9 +34,9 @@
 			@udpSocket.bind @options.listenPort

 		sendMessage: (message, address) ->
-			# console.log 'send message', message
-			data = bencode.encode message
-			@udpSocket.send data, 0, data.length, address.port, address.ip
+			if address.port > 0 and address.port < 65536
+				data = bencode.encode message
+				@udpSocket.send data, 0, data.length, address.port, address.ip

 		close: () ->
 			@udpSocket.close


Executing onSave callback inside form editor

--- client/new_object.cjsx
+++ client/new_object.cjsx
@@ -5,7 +5,7 @@
     constructor: (props) ->
         super props
         @state =
-            instance: new this.props.type()     # note that this object must not reactivelly change
+            instance: new this.props.type()

     mixins: [ReactMeteorData],

@@ -23,11 +23,12 @@
         fields

     afterSave: (errors)=>
-        console.log "in afterSave userID: "+ Meteor.userId
+        console.log "in afterSave "
         if errors
             #todo: print error in form
             console.log "in afterSave errors received"
-
+        else
+            @props.onSave @state.instance


engine data module

engine data module

--- src/engine_data.coffee
+++ src/engine_data.coffee
@@ -2,7 +2,9 @@
 The Data module provides methods to store global and persistent data in the engine.

     engine.data.score = 0
-    engine.data.score +=
+    engine.data.score += 10
+
+

 @name Data
 @fieldOf Engine


Tweak handling of current week

Tweak handling of current week

--- src/summary.coffee
+++ src/summary.coffee
@@ -157,10 +157,8 @@
     calledMethod = "week"
     startOfWeek = config.startOfWeek or "Monday"

-    if date
-        date = "#{startOfWeek} before #{date}"
-    else
-        date = "last #{startOfWeek}"
+    date = "today" unless date
+    date = "#{startOfWeek} before #{date}"

     first = getDate date
     last = new Date first.getTime()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -1,5 +1,5 @@
 ###*
-The `Flickerable` module provides a method to flicker a sprite between its curre and a given opacity (alpha value).
+The `Flickerable` module provides a method to flicker a sprite between its current opacity (alpha) and a given opacity.

     player = GameObject
       alpha: 0.9


formatting

formatting

--- src/twitter.coffee
+++ src/twitter.coffee
@@ -143,6 +143,7 @@

         @subscribe @replies
         @subscribe @user
+
 class TwitterListConversationTimeline extends TwitterTimeline
     constructor: (username, listname, options) ->
         @list = new TwitterListTimeline username, listname, options


monitor fw checkins, simplify show add and del syntax

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -21,7 +21,7 @@
 sprintf = require("sprintf-js").sprintf

 modulename = 'fw'
-blacklistfile = modulename + ".json"
+fwdata_file = modulename + ".json"
 displayfmt = '%-4s %-50s %-15s %s'
 timefmt = 'YYYY-MM-DD HH:mm:ss ZZ'
 svcQueueIntervalMs = 300 * 1000
@@ -31,6 +31,22 @@
   notify: []
   blacklist: []
   whitelist: []
+  firewalls: []
+
+fwnames =
+  '10.9.0.252': 'Fairbanks-1'
+  '10.9.0.253': 'Fairbanks-2'
+  '10.9.128.252': 'Anchorage-1'
+  '10.9.128.253': 'Anchorage-2'
+  '10.9.192.252': 'Juneau-1'
+  '10.9.192.253': 'Juneau-2'
+
+sortBy = (key, a, b, r) ->
+  r = if r then 1 else -1
+  return -1*r if a[key] > b[key]
+  return +1*r if a[key] < b[key]
+  return 0
+

 isAuthorized = (msg) ->
   console.error 'bad robotRef' unless robotRef
@@ -72,7 +88,7 @@
       "Change will be applied in < 5 minutes. Removed: " +
       "```"+ deleted.join("\n") + "```"
     notifySubscribers msg
-    fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'
+    fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'

 notifySubscribers = (msg, current_un = false) ->
   console.error 'bad robotRef' unless robotRef
@@ -80,28 +96,60 @@
     robotRef.send { room: un }, msg unless current_un && un == current_un

 addListEntry = (robot, msg) ->
+  fullcmd = String(msg.match.shift())
   who = msg.envelope.user.name
-
-  list_name = String(msg.match[1])
-  list_name = 'whitelist' if list_name.indexOf('w') == 0
-  list_name = 'blacklist' if list_name.indexOf('b') == 0

   entry =
     creator: who
     created: moment().format()
     expires: moment().add(1, 'months').format()
-    type: String(msg.match[2])
-    val: String(msg.match[3])
-
-  if entry.val.toLowerCase().indexOf('https://') == 0
-    usermsg = "#{list_name}ing of https links not supported."
-    return msg.reply usermsg
-
-  if entry.val.toLowerCase().indexOf('http') == 0
-    entry.val = entry.val.replace(/http:\/\//i,'')
-
-  if msg.match[4]?
-    expires = String(msg.match[4])
+
+  list_name = String(msg.match.shift())
+  list_name = 'whitelist' if list_name.indexOf('w') == 0
+  list_name = 'blacklist' if list_name.indexOf('b') == 0
+  #console.log list_name
+
+  l_val = String(msg.match.shift())
+  if extra = l_val.match /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2}|))$/
+    entry.type = 'cidr'
+    entry.val = extra[1]
+
+    # safety check !!
+    if entry.val.match /^(?:137\.229\.|199\.165\.)/
+      usermsg = "Blocking CIDRs begining with 137.229. or 199.165. not allowed. "+
+        " If this is time critical ask a human for help; otherwise open a ticket."
+      return msg.reply usermsg
+
+  else if extra = l_val.match /^([a-zA-Z][a-zA-Z0-9\.]+)$/
+    entry.type = 'domain'
+    entry.val = extra[1]
+
+    # safety check !!
+    if entry.val.toLowerCase().match /(?:alaska|uaf)\.edu$/
+      usermsg = "Blocking alaska.edu or uaf.edu not allowed. "+
+        " If this is time critical ask a human for help; otherwise open a ticket."
+      return msg.reply usermsg
+
+  else
+    entry.type = 'url'
+    entry.val = l_val
+    if entry.val.toLowerCase().indexOf('https://') == 0
+      usermsg = "#{list_name}ing of https links not supported."
+      return msg.reply usermsg
+    if entry.val.toLowerCase().indexOf('http://') == 0
+      entry.val = entry.val.replace(/http:\/\//i,'')
+
+    # safety check !!
+    if entry.val.toLowerCase().match /[^\/]+(?:alaska|uaf)\.edu/
+      usermsg = "Blocking alaska.edu or uaf.edu not allowed. "+
+        " If this is time critical ask a human for help; otherwise open a ticket."
+      return msg.reply usermsg
+
+  #console.log "#{l_val}: #{entry.type} => #{entry.val}"
+
+  expires = String(msg.match.shift())
+  if expires isnt 'undefined'
+    #console.log "processing expiration: #{expires}"
     extra = expires.match /\+(\d)([hdwMQy])/
     if extra?
       n = extra[1]
@@ -118,7 +166,7 @@
   robot.logger.info logmsg

   fwdata[list_name].push entry
-  fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'
+  fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'

   usermsg = "#{who} added `#{entry.val}` to firewall #{list_name}. " +
     "Expires `#{entry.expires}`.  Change will be applied in < 5 minutes."
@@ -133,24 +181,26 @@

 deleteListEntry = (robot, msg) ->
+  fullcmd = String(msg.match.shift())
   who = msg.envelope.user.name

-  list_name = String(msg.match[1])
+  list_name = String(msg.match.shift())
   list_name = 'whitelist' if list_name.indexOf('w') == 0
   list_name = 'blacklist' if list_name.indexOf('b') == 0

-  l_type = String(msg.match[2])
-  l_search = String(msg.match[3])
+  #l_type = String(msg.match.shift())
+  l_type = false
+  l_search = String(msg.match.shift())

   if l_search.toLowerCase().indexOf('https://') == 0
     usermsg = "#{list_name}ing of https links not supported."
     return msg.reply usermsg

-  if l_search.toLowerCase().indexOf('http') == 0
+  if l_search.toLowerCase().indexOf('http://') == 0
     l_search = l_search.replace(/http:\/\//i,'')

   logmsg = "#{modulename}: #{who} requested: " +
-    "#{list_name} delete #{l_type} #{l_search}"
+    "#{list_name} delete #{l_type ? l_type : ''} #{l_search}"
   robot.logger.info logmsg

   deleted = []
@@ -181,7 +231,7 @@
       "Change will be applied in < 5 minutes. Removed: " +
       "```"+ deleted.join("\n") + "```"
     fwdata[list_name] = new_entry
-    fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'
+    fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'
   else
     usermsg = "#{list_name} delete request did not match any records."
   msg.reply usermsg
@@ -201,10 +251,9 @@
   list_name = 'blacklist' if list_name.indexOf('b') == 0

   l_type = false
-  l_type = msg.match[2] if msg.match[2]
-
   l_search = false
-  l_search = msg.match[3] if msg.match[3]
+  if msg.match[2]?
+    l_search = String(msg.match[2])

   logmsg = "#{modulename}: #{msg.envelope.user.name} requested: show #{list_name}"
   robot.logger.info logmsg
@@ -250,7 +299,7 @@
     "added #{who} to firewall change notifications"
   robot.logger.info logmsg

-  fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'
+  fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'

 unsubscribe = (robot, msg) ->
@@ -271,7 +320,7 @@
     "removed #{who} from firewall change notifications"
   robot.logger.info logmsg

-  fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'
+  fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'

 showSubscribers = (robot, msg) ->
@@ -286,10 +335,14 @@
   logmsg = "#{modulename}: robot responded to #{who}: " +
     fwdata.notify.join(', ')
   robot.logger.info logmsg
+

 httpGetList = (robot, req, res, list_name, l_type) ->
   clientip = req.connection.remoteAddress || req.socket.remoteAddress ||
     req.connection.socket.remoteAddress
+
+  rememberCheckin clientip, list_name, l_type
+
   logmsg = "#{modulename}: web request from #{clientip}: get #{l_type} #{list_name}"
   robot.logger.info logmsg

@@ -306,13 +359,51 @@
   robot.logger.info logmsg

+showCheckins = (robot, msg) ->
+  who = msg.envelope.user.name
+
+  logmsg = "#{modulename}: #{who} requested: checkins"
+  robot.logger.info logmsg
+
+  arr = []
+  for obj in fwdata.firewalls
+    obj.checkin = moment(obj.checkin) if typeof(obj.checkin) is 'string'
+    arr.push sprintf '%-18s %-10s %-8s %-15s', obj.name, obj.list, obj.type,
+      obj.checkin.fromNow()
+  usermsg = "Expect firewall check times: ```"+ arr.join("\n") + "```"
+  msg.reply usermsg
+
+  logmsg = "#{modulename}: robot responded to #{who}: checkins"
+  robot.logger.info logmsg
+
+
+rememberCheckin = (clientip,list_name,l_type) ->
+  return unless fwname = fwnames[clientip] # skip non-firewalls
+  for obj in fwdata.firewalls
+    if obj.ip is clientip and obj.type is l_type and obj.list is list_name
+      obj.checkin = moment().add(5,'minutes')
+      fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'
+      return
+  # otherwise create new object
+  obj =
+    ip: clientip
+    name: fwname
+    list: list_name
+    type: l_type
+    checkin: moment().add(5,'minutes')
+  fwdata.firewalls.push obj
+  if fwdata.firewalls.length > 1
+    fwdata.firewalls = fwdata.firewalls.sort (a,b) ->
+      sortBy('name',a,b) or sortBy('list',a,b) or sortBy('type',a,b)
+  fs.writeFileSync fwdata_file, JSON.stringify(fwdata), 'utf-8'
+
 module.exports = (robot) ->

   robotRef = robot
   setTimeout expirationWorker, svcQueueIntervalMs

   try
-    fwdata = JSON.parse fs.readFileSync blacklistfile, 'utf-8'
+    fwdata = JSON.parse fs.readFileSync fwdata_file, 'utf-8'
   catch error
     console.log('Unable to read file', error) unless error.code is 'ENOENT'

@@ -331,9 +422,9 @@
   robot.respond /(?:firewall|fw)(?: help| h|)$/, (msg) ->
     cmds = ['```']
     arr = [
-      modulename + " show (white|black)list [(cidr|url) [searchterm]]"
-      modulename + " add (white|black)list (url|cidr) <weburl.tld/etc|x.x.x.x>"
-      modulename + " del (white|black)list (url|cidr) <weburl.tld/etc|x.x.x.x>"
+      modulename + " show (white|black)list [searchterm]"
+      modulename + " add (white|black)list <weburl.tld/etc|x.x.x.x>"
+      modulename + " del (white|black)list <weburl.tld/etc|x.x.x.x>"
       modulename + " subscribe [username] - subscribe to change notifications"
       modulename + " unsubscribe [username]"
       modulename + " show subscribers"
@@ -353,10 +444,16 @@

     return

-  robot.respond /(?:firewall|fw) show (whitelist|wl|w|blacklist|bl|b)(?: (cidr|url)(?: ([^ ]+)|)|)$/i, (msg) ->
+  robot.respond /(?:firewall|fw) show (?:checkins)$/i, (msg) ->
+    return showCheckins robot, msg
+
+  robot.respond /(?:firewall|fw) show (?:subscribers|s)$/i, (msg) ->
+    return showSubscribers robot, msg
+
+  robot.respond /(?:firewall|fw) show (whitelist|wl|w|blacklist|bl|b)(?: (.+)|)$/i, (msg) ->
     return showList robot, msg

-  robot.respond /(?:firewall|fw) add (whitelist|wl|w|blacklist|bl|b) (url|cidr) ([^ ]+)(?: ([^ ]+)|)$/i, (msg) ->
+  robot.respond /(?:firewall|fw) add (whitelist|wl|w|blacklist|bl|b) ([^ ]+)(?: ([^ ]+)|)$/i, (msg) ->
     return unless isAuthorized msg
     return unless is2fa msg

@@ -371,9 +468,6 @@
   robot.respond /(?:firewall|fw) (?:subscribe|sub)(?: ([^ ]+))$/i, (msg) ->
     return subscribe robot, msg

-  robot.respond /(?:firewall|fw) show (?:subscribers|s)$/i, (msg) ->
-    return showSubscribers robot, msg
-
   robot.respond /(?:firewall|fw) (?:unsubscribe|unsub)(?: ([^ ]+))$/i, (msg) ->
     return unsubscribe robot, msg


Stop pulling after disconnect + remember lastMd5

--- app/src/utils/storage/synced_store.coffee
+++ app/src/utils/storage/synced_store.coffee
@@ -16,7 +16,10 @@
     @client = new ledger.api.SyncRestClient(addr)
     @throttled_pull = _.throttle _.bind(@._pull,@), @PULL_THROTTLE_DELAY
     @debounced_push = _.debounce _.bind(@._push,@), @PUSH_DEBOUNCE_DELAY
-    _.defer => @_initConnection()
+    _.defer =>
+      ledger.storage.wallet.get ['__last_sync_md5'], (item) =>
+        @lastMd5 = item.__last_sync_md5
+        @_initConnection()

   # Stores one or many item
   #
@@ -42,40 +45,16 @@

   # @return A jQuery promise
   _pull: ->
-    d = jQuery.Deferred()
     @client.get_settings_md5().then( (md5) =>
-      if md5 != @lastMd5
-        @client.get_settings().catch(_.bind(d.reject,d)).then (items) =>
-          @mergeStrategy(items).then =>
-            @lastMd5 = md5
-            d.resolve(items)
-      else
-        d.resolve()
+      return undefined if md5 == @lastMd5
+      @client.get_settings().then (items) =>
+        @mergeStrategy(items).then =>
+          @_setLastMd5(md5)
+          items
     ).catch( (jqXHR) =>
       # Data not synced already
-      if jqXHR.status == 404
-        this.init(cb, ecb)
-      else
-        d.reject(jqXHR)
-    )
-    d.promise()
-
-  # @return A jQuery promise
-  _pull2: ->
-    @client.get_settings_md5().then( (md5) =>
-      if md5 != @lastMd5
-        return @client.get_settings().then (items) =>
-          return @mergeStrategy(items).then =>
-            @lastMd5 = md5
-            return items
-      else
-        return undefined
-    ).catch( (jqXHR) =>
-      # Data not synced already
-      if jqXHR.status == 404
-        return this.init()
-      else
-        return jqXHR
+      return this.init() if jqXHR.status == 404
+      jqXHR
     )

   # @return A jQuery promise
@@ -87,7 +66,7 @@
         settings[raw_key] = raw_value if raw_key.match(@_nameRegex)
       @__retryer (ecbr) =>
         @client.put_settings(settings).catch(ecbr).then (md5) =>
-          @lastMd5 = md5
+          @_setLastMd5(md5)
           d.resolve(md5)
       , _.bind(d.reject,d)
     , _.bind(d.reject,d)
@@ -116,7 +95,7 @@
   _initConnection: ->
     @__retryer (ecbr) =>
       @client.get_settings_md5().then( =>
-        setInterval(@throttled_pull, @PULL_INTERVAL_DELAY)
+        @pullTimer = setInterval(@throttled_pull, @PULL_INTERVAL_DELAY)
       ).catch (jqXHR) =>
         # Data not synced already
         if jqXHR.status == 404
@@ -126,6 +105,8 @@
           console.error("BadRequest during SyncedStore initialization:", jqXHR)
         else
           ecbr(jqXHR)
+    ledger.app.wallet.once 'state:changed', =>
+      clearInterval(@pullTimer) if ledger.app.wallet._state != ledger.wallet.States.UNLOCKED

   # @param [Function] cb A callback invoked once init is done. cb()
   # @param [Function] ecb A callback invoked when init fail. Take $.ajax.fail args.
@@ -138,8 +119,13 @@
         settings[raw_key] = raw_value if raw_key.match(@_nameRegex)
       @__retryer (ecbr) =>
         @client.post_settings(settings).catch(ecbr).then (md5) =>
-          @lastMd5 = md5
+          @_setLastMd5(md5)
           d.resolve(md5)
       , _.bind(d.reject,d)
     , _.bind(d.reject,d)
     d.promise()
+
+  # Save lastMd5 in settings
+  _setLastMd5: (md5) ->
+    @lastMd5 = md5
+    ledger.storage.wallet.set("__last_sync_md5": md5)


Applying Scott's fix for #1198.

Applying Scott's fix for #1198.

--- app/views/modal/save_version_modal.coffee
+++ app/views/modal/save_version_modal.coffee
@@ -57,7 +57,6 @@
     }
     errors = patch.validate()
     forms.applyErrorsToForm(@$el, errors) if errors
-    patch.set 'editPath', document.location.pathname
     res = patch.save()
     return unless res
     @enableModalInProgress(@$el)


fixing typo in previous commit

fixing typo in previous commit

--- jsapp/xlform/src/view.row.templates.coffee
+++ jsapp/xlform/src/view.row.templates.coffee
@@ -33,7 +33,7 @@

 Settings

#{_t("Question Options")}

Skip Logic

-
_t("Validation Criteria")

+
#{_t("Validation Criteria")}

#{_t("Response Type")}


Add check to TomeView

Add check to TomeView

Found a bug while playing Ogre Encampment: click the Chest before clicking past the
intro screen and the game throws an error, when `updateSpellPalette` tries to access
call `spell.getSource()` but `spell` is undefined. This check keeps the same behavior
but handles the error.

--- app/views/play/level/tome/TomeView.coffee
+++ app/views/play/level/tome/TomeView.coffee
@@ -202,7 +202,7 @@
     spell = @spellFor thang, spellName
     unless spell?.canRead()
       @clearSpellView()
-      @updateSpellPalette thang, spell
+      @updateSpellPalette thang, spell if spell
       return
     unless spell.view is @spellView
       @clearSpellView()