Make chatmgr use form semantics

Make chatmgr use form semantics

--- Web/js/classes/chat.coffee
+++ Web/js/classes/chat.coffee
@@ -1,13 +1,14 @@
 class window.ChatManager
 	constructor: (@rohbot) ->
 		@chat  = $ '#chat'
+		@form  = $ '#chat-form'
 		@input = $ '#messageBox'
-		@send  = $ '#send'

 		@input.on 'keydown', @processEnter
-		@send.on  'click',   @processSend
+		@form.on  'submit',   @processSend

-	processSend: =>
+	processSend: (e) =>
+		e.preventDefault()
 		message = @input.val()
 		return unless message.length

@@ -20,8 +21,7 @@
 		# Converted from http://stackoverflow.com/a/3533099/1056845
 		return unless event.keyCode == 13
 		unless event.ctrlKey
-			@input.blur().focus()
-			@send.click()
+			@form.submit()
 			return
 		dom = @input[0]
 		if typeof dom.selectionStart == "number" and typeof dom.selectionEnd == "number"


Link to GitHub in essential.js

Link to GitHub in essential.js

--- _includes/essential.coffee
+++ _includes/essential.coffee
@@ -2,6 +2,7 @@
 # Essential.js 1.1.20
 # @author Cedric Ruiz
 # @license MIT
+# https://github.com/elclanrs/essential.js/
 ###

 # Core


Add 'Git-Plus' to tooltip for output view toggler

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -159,7 +159,7 @@
     link = document.createElement 'a'
     link.appendChild icon
     link.onclick = (e) -> OutputViewManager.getView().toggle()
-    atom.tooltips.add div, { title: "Toggle Output Console"}
+    atom.tooltips.add div, { title: "Toggle Git-Plus Output Console"}
     div.appendChild link
     @statusBarTile = statusBar.addRightTile item: div, priority: 0


added selection based code execution for C++ & C++14 in macOS

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -138,6 +138,12 @@

   'C++':
     if GrammarUtils.OperatingSystem.isDarwin()
+      "Selection Based":
+        command: "bash"
+        args: (context) ->
+          code = context.getCode(true)
+          tmpFile = GrammarUtils.createTempFileWithCode(code, ".cpp")
+          ["-c", "xcrun clang++ -fcolor-diagnostics -std=c++14 -Wall -include stdio.h -include iostream '" + tmpFile + "' -o /tmp/cpp.out && /tmp/cpp.out"]
       "File Based":
         command: "bash"
         args: (context) -> ['-c', "xcrun clang++ -fcolor-diagnostics -std=c++14 -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
@@ -158,6 +164,12 @@

   'C++14':
     if GrammarUtils.OperatingSystem.isDarwin()
+      "Selection Based":
+        command: "bash"
+        args: (context) ->
+          code = context.getCode(true)
+          tmpFile = GrammarUtils.createTempFileWithCode(code, ".cpp")
+          ["-c", "xcrun clang++ -fcolor-diagnostics -std=c++14 -Wall -include stdio.h -include iostream '" + tmpFile + "' -o /tmp/cpp.out && /tmp/cpp.out"]
       "File Based":
         command: "bash"
         args: (context) -> ['-c', "xcrun clang++ -fcolor-diagnostics -std=c++14 -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]


Set Euler XYZ rotation as default when creating objects from code.

--- engine/gameobject.coffee
+++ engine/gameobject.coffee
@@ -32,7 +32,7 @@
         @position = vec3.create()
         @rotation = quat.create()
         @radius = 0
-        @rotation_order = 'Q'
+        @rotation_order = 'XYZ'
         @scale = vec3.fromValues 1, 1, 1
         @dimensions = vec3.create()
         @color = vec4.fromValues 1, 1, 1, 1


Fixed because text not always saving [#143497999]

Changed from onBlur to onChange handler because when you click on the canvas the textarea is removed before the blur handler can get the value.

--- src/code/views/link-relation-view.coffee
+++ src/code/views/link-relation-view.coffee
@@ -188,7 +188,7 @@
         (textarea
           defaultValue: @props.link.reasoning
           placeholder: tr "~NODE-RELATION-EDIT.BECAUSE_PLACEHOLDER"
-          onBlur: @updateReasoning
+          onChange: @updateReasoning
           ref: 'reasoning'
           className: 'full'
           rows: 3


:lipstick:

:lipstick:

--- src/app/package-config-view.coffee
+++ src/app/package-config-view.coffee
@@ -53,12 +53,12 @@
     if homepage
       @dropdown.append $$ ->
         @li =>
-          @a "Visit homepage", href: homepage
+          @a 'Visit homepage', href: homepage

     if issues = @pack.bugs?.url
       @dropdown.append $$ ->
         @li =>
-          @a "Report issue", href: issues
+          @a 'Report issue', href: issues

     @dropdown.on 'click', => @dropdown.hide()


refresh instantly after any changes

refresh instantly after any changes

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -378,6 +378,7 @@
       , (e)=>
         doneTransfer e
         unless e
+          @m_changed_path = destpath.replace /[^\/]+$/, ''
           msg = Messenger().post
             message: "文件 #{filename} 上传完毕"
             actions:
@@ -544,7 +545,9 @@
                             $btnCancelDel.show().click @upyun_api
                               method: "DELETE"
                               url: url
-                              , operationDone
+                              , (e)=>
+                                @m_changed_path = url.replace /[^\/]+\/$/, ''
+                                operationDone e
         else
           $ document.createElement 'button'
             .appendTo td
@@ -559,7 +562,9 @@
                 $btnCancelDel.show().click @upyun_api
                   method: "DELETE"
                   url: url
-                  , operationDone
+                  , (e)=>
+                    @m_changed_path = url.replace /\/[^\/]+$/, '/'
+                    operationDone e
         if file.isDirectory
           $ document.createElement 'button'
             .appendTo td
@@ -665,22 +670,25 @@
   @messengerTasks = $ document.createElement 'ul'
     .appendTo 'body'
     .messenger()
+  forverCounter = 0
   @async.forever (doneForever)=>
       if @m_active && !@shortOperationBusy
-        @refresh_filelist (e)=>
-          if e
-            msg = Messenger().post
-              message: e.message
-              type: 'error'
-              actions:
-                ok:
-                  label: '确定'
-                  action: =>
-                    msg.hide()
-            @jump_login()
-          setTimeout (=>doneForever null), 1000
-      else
-        setTimeout (=>doneForever null), 100
+        forverCounter += 1
+        if forverCounter == 20 || @m_changed_path == @m_path
+          forverCounter = 0
+          return @refresh_filelist (e)=>
+            if e
+              msg = Messenger().post
+                message: e.message
+                type: 'error'
+                actions:
+                  ok:
+                    label: '确定'
+                    action: =>
+                      msg.hide()
+              @jump_login()
+            setTimeout (=>doneForever null), 100
+      setTimeout (=>doneForever null), 100
     , (e)=>
       throw e


Allows for matching user names with an optional @

--- src/mimic.coffee
+++ src/mimic.coffee
@@ -66,7 +66,7 @@
     all_model.learn msg.message.text

   # Generate markov chains on demand, optionally seeded by some initial state.
-  robot.respond /MIMIC\s+(\w+)(\s+(.+))?$/i, (msg) ->
+  robot.respond /MIMIC\s+@?(\w+)(\s+(.+))?$/i, (msg) ->
     user_name = msg.match[1]
     model = getModel(user_name)
     if not model


name on class no work

name on class no work

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1181,8 +1181,6 @@
     @ensureConstructor name
     @body.spaced = yes
     @body.expressions.unshift @ctor unless @ctor instanceof Code
-    if decl
-      @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal 'name']), (new Literal "'#{name}'")
     @body.expressions.push lname
     @body.expressions.unshift @directives...
     @addBoundFunctions o


CoffeeScript madness with regex

CoffeeScript madness with regex

--- widgets/statuscake/statuscake.coffee
+++ widgets/statuscake/statuscake.coffee
@@ -6,7 +6,6 @@
   onData: (data) ->
     node = $(@node)
     status = data.overall_status
-    node.removeClass (function (index, css) {
-        return (css.match (/(^|\s)status-\S+/g) || []).join(' ');
-    });
+    node.removeClass (index, css) ->
+      (css.match(/(^|\s)status-\S+/g) or []).join ' '
     node.addClass "status-#{status}"


Remove extra debugging

Remove extra debugging

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -231,14 +231,11 @@
       else
         Messages.update parent,
           $push: children: child
-    console.log child, parent, root, 'vs.', cmsg.root
     if root != cmsg.root
-      console.log 'setting root'
       Messages.update child,
         $set: root: root
       if cmsg.root?
         ## If we move a nonroot message to have new root, update descendants.
-        console.log 'a'
         descendants = descendantMessageIds child
         if descendants.length > 1
           Messages.update
@@ -249,7 +246,6 @@
             multi: true
       else
         ## To reparent root message, change the root of all descendants.
-        console.log 'b', child, root
         Messages.update
           root: child
         ,


:art: Add EOL to messages

:art: Add EOL to messages

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -51,4 +51,4 @@
     @emitter.dispose()

-module.exports = MessageRegistry+module.exports = MessageRegistry


:art: :tada: finally refactor Replace operator

Now I’ve rewritten every code in operator.coffee.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -1,4 +1,4 @@
-# Refactoring status: 80%
+# Refactoring status: 100%
 LineEndingRegExp = /(?:\n|\r\n)$/
 _ = require 'underscore-plus'
 {Point, Range, CompositeDisposable} = require 'atom'
@@ -711,52 +711,32 @@

 # Replace
 # -------------------------
-# [FIXME] need rewrite
 class Replace extends Operator
   @extend()
   input: null
   hover: icon: ':replace:', emoji: ':tractor:'
+  flashTarget: false
   trackChange: true
   requireInput: true
   requireTarget: false

   initialize: ->
+    @setTarget @new('MoveRight') if @isMode('normal')
     @focusInput()

   isComplete: ->
     @input = "\n" if @input is ''
     super

-  execute: ->
-    count = @getCount()
-
-    @editor.transact =>
-      if @target?
-        if @selectTarget()
-          @editor.replaceSelectedText null, (text) =>
-            text.replace(/./g, @input)
-          for selection in @editor.getSelections()
-            point = selection.getBufferRange().start
-            selection.setBufferRange(Range.fromPointWithDelta(point, 0, 0))
-      else
-        for cursor in @editor.getCursors()
-          pos = cursor.getBufferPosition()
-          currentRowLength = @editor.lineTextForBufferRow(pos.row).length
-          continue unless currentRowLength - pos.column >= count
-
-          _.times count, =>
-            point = cursor.getBufferPosition()
-            @editor.setTextInBufferRange(Range.fromPointWithDelta(point, 0, 1), @input)
-            cursor.moveRight()
-          cursor.setBufferPosition(pos)
-
-        # Special case: when replaced with a newline move to the start of the
-        # next row.
-        if @input is "\n"
-          _.times count, =>
-            @editor.moveDown()
-          @editor.moveToFirstCharacterOfLine()
-
+  shouldReplace: (text) ->
+    not (@target.instanceof('MoveRight') and (text.length isnt @getCount()))
+
+  execute: ->
+    @eachSelection (s, setPoint) =>
+      text = s.getText().replace(/./g, @input)
+      if @shouldReplace(text)
+        s.insertText(text, autoIndentNewline: true)
+      setPoint() unless @input is "\n"
     @activateMode('normal')

 # Insert entering operation


Add catch case in pigments commands

Add catch case in pigments commands

--- lib/pigments.coffee
+++ lib/pigments.coffee
@@ -173,7 +173,11 @@
       pane ||= atom.workspace.getActivePane()

       atom.workspace.openURIInPane(uri, pane, {})
+    .catch (reason) ->
+      console.error reason

   reloadProjectVariables: ->
     @project.initialize().then =>
       @project.loadPathsAndVariables()
+    .catch (reason) ->
+      console.error reason


Faster clone

Faster clone

--- lib/prefixer.coffee
+++ lib/prefixer.coffee
@@ -5,7 +5,6 @@

 # Recursivly clone objects
 clone = (obj, parent) ->
-  return obj if typeof(obj) != 'object'
   cloned = new obj.constructor()

   for own i, value of obj
@@ -16,7 +15,8 @@
     else if value instanceof Array
       cloned[i] = value.map (i) -> clone(i, cloned)
     else if i != '_autoprefixerPrefix' and i != '_autoprefixerValues'
-      cloned[i] = clone(value, cloned)
+      value = clone(value, cloned) if typeof(value) == 'object'
+      cloned[i] = value

   cloned


Support <ol start>

Support <ol start>

--- lib/sanitize.coffee
+++ lib/sanitize.coffee
@@ -24,6 +24,7 @@

 ## Additional HTML features
 sanitizeHtml.defaults.allowedTags.push 's'
+sanitizeHtml.defaults.allowedAttributes.ol = ['start']

 ## LaTeX features
 sanitizeHtml.defaults.allowedTags.push 'u'  ## \underline


Cleanup

Cleanup

--- SingularityUI/app/views/task.coffee
+++ SingularityUI/app/views/task.coffee
@@ -22,16 +22,8 @@
             @render()

             @taskFiles = new TaskFiles {}, { taskId: @options.taskId, offerHostname: @taskHistory.attributes.task.offer.hostname, directory: @taskHistory.attributes.directory }
-            # @taskFiles.fetch().done =>
-            #     @taskFilesFetchDone = true
-            #     @render()
-
-            log 'asdasdasd'
-            #log @taskFiles.fetch()
-
             @taskFiles.testSandbox()
                 .done(=>
-                    log 'done'
                     @taskFiles.fetch().done =>
                         @taskFilesFetchDone = true
                         @render()


use change helper to set value

use change helper to set value

--- spec/lanes/components/grid/RowEditorSpec.coffee
+++ spec/lanes/components/grid/RowEditorSpec.coffee
@@ -28,8 +28,9 @@
         editor = Lanes.Test.Utils.findRenderedComponentWithType(
             grid, Lanes.Components.Grid.RowEditor
         )
-        _.dom(editor).qs('.field:nth-of-type(2) input').setValue(value)
+        _.dom(editor).qs('.field:nth-of-type(2) input').change(target: {value})
         res({grid, editor})
+

 CommonSpecs = ->
@@ -53,6 +54,7 @@
             done()

     it 'does not update when edit is canceled', (done) ->
+        expect( @query.results.rowAt(1)[1] ).toEqual('TEST2')
         RenderEdit(@query, 'CANCKED').then ({editor}) =>
             _.dom(editor).qs('.btn.cancel').click()
             expect( @query.results.rowAt(1)[1] ).toEqual('TEST2')


:art: Aggregate other subscriptions

:art: Aggregate other subscriptions

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -40,34 +40,34 @@
       @updateTooltip()

     if typeof @item.onDidChangeTitle is 'function'
-      @titleSubscription = @item.onDidChangeTitle(titleChangedHandler)
+      @subscriptions.add @item.onDidChangeTitle(titleChangedHandler)
     else if typeof @item.on is 'function'
       #TODO Remove once old events are no longer supported
       @item.on('title-changed', titleChangedHandler)
-      @titleSubscription = dispose: =>
+      @subscriptions.add dispose: =>
         @item.off?('title-changed', titleChangedHandler)

     iconChangedHandler = =>
       @updateIcon()

     if typeof @item.onDidChangeIcon is 'function'
-      @iconSubscription = @item.onDidChangeIcon? =>
+      @subscriptions.add @item.onDidChangeIcon? =>
         @updateIcon()
     else if typeof @item.on is 'function'
       #TODO Remove once old events are no longer supported
       @item.on('icon-changed', iconChangedHandler)
-      @iconSubscription = dispose: =>
+      @subscriptions.add dispose: =>
         @item.off?('icon-changed', iconChangedHandler)

     modifiedHandler = =>
       @updateModifiedStatus()

     if typeof @item.onDidChangeModified is 'function'
-      @modifiedSubscription = @item.onDidChangeModified(modifiedHandler)
+      @subscriptions.add @item.onDidChangeModified(modifiedHandler)
     else if typeof @item.on is 'function'
       #TODO Remove once old events are no longer supported
       @item.on('modified-status-changed', modifiedHandler)
-      @modifiedSubscription = dispose: =>
+      @subscriptions.add dispose: =>
         @item.off?('modified-status-changed', modifiedHandler)

     if typeof @item.onDidSave is 'function'
@@ -77,7 +77,7 @@
           @path = event.path
           @setupVcsStatus() if atom.config.get 'tabs.enableVcsColoring'

-    @configSubscription = atom.config.observe 'tabs.showIcons', =>
+    @subscriptions.add atom.config.observe 'tabs.showIcons', =>
       @updateIconVisibility()

     @subscriptions.add atom.config.observe 'tabs.enableVcsColoring', (isEnabled) =>
@@ -118,12 +118,8 @@
     @tooltip?.dispose()

   destroy: ->
-    @titleSubscription?.dispose()
-    @modifiedSubscription?.dispose()
-    @iconSubscription?.dispose()
+    @subscriptions?.dispose()
     @mouseEnterSubscription?.dispose()
-    @configSubscription?.dispose()
-    @subscriptions?.dispose()
     @repoSubscriptions?.dispose()
     @destroyTooltip()
     @remove()


Fix style.

Fix style.

--- lib/twitter_.coffee
+++ lib/twitter_.coffee
@@ -81,7 +81,7 @@
         return json

-    ### Friendship ###
+    # Friendship

     existsFriendship: (userA, userB, _) ->

@@ -103,6 +103,10 @@
         @post '/friendships/create.json', params, null, _

+
+
+# Exports
+
 module.exports = Twitter


support for untitled editors

support for untitled editors

--- lib/ui/views.coffee
+++ lib/ui/views.coffee
@@ -40,15 +40,32 @@
         e.stopPropagation()
     view

+  openEditorById: (id, line) ->
+    for pane in atom.workspace.getPanes()
+      ind = 0
+      for item in pane.getItems()
+        if item.constructor.name is "TextEditor" and item.getBuffer().id is id
+          pane.activateItemAtIndex ind
+          item.setCursorBufferPosition [line, 0]
+          item.scrollToCursorPosition()
+          return true
+        ind += 1
+    false
+
+  getUntitledId: (file) -> file.match(/untitled-([\d\w]+)$/)?[1]
+
   link: ({file, line, contents}) ->
     view = @render @tags.a {href: '#'}, contents
-    # TODO: if file is untitled, we need to focus the TextEditor by its ID and set the
-    # tooltip to 'untitled'
-    atom.tooltips.add view, title: -> file
-    view.onclick = ->
-      atom.workspace.open file,
-        initialLine: if line >= 0 then line
-        searchAllPanes: true
+    if id = @getUntitledId file
+      atom.tooltips.add view, title: -> 'untitled'
+      view.onclick = =>
+        @openEditorById id, line
+    else
+      atom.tooltips.add view, title: -> file
+      view.onclick = ->
+        atom.workspace.open file,
+          initialLine: if line >= 0 then line
+          searchAllPanes: true
     view

   number: ({value, full}) ->


Fix for review

Fix for review

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3441,10 +3441,10 @@
                     if next.length is 1
                         next = "0" + next
                     snippet += next
-                    if i > 4
+                    if i > 4 and i isnt size-1
                         snippet += "..."
                         break
-                    else
+                    else if i isnt str.length-1
                         snippet += " "
                 return "<binary, #{sizeStr}, \"#{snippet}\">"


update

update

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -53,8 +53,10 @@
   e_width = 310
   timeline = new Timeline ".tlelm"

-  if w_width > 850
-    timeline.timeline(w_width-200, e_width, 90, 30)
+  if w_width > 1200
+    timeline.timeline(1200, e_width, 90, 30)
+  else if w_width > 850
+    timeline.timeline(800, e_width, 90, 30)
   else
     timeline.reset_timeline()


photo clipping uses HexBox attributes

photo clipping uses HexBox attributes

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -10,17 +10,17 @@

     @events = new EventHandler(this)

-    @pattern = new PuzzlePattern(this)
+    @puzzle_pattern = new PuzzlePattern(this)
     @mask = new MissingPiecesMask(this)
     @piece = new PuzzlePiece(this)

-    @pattern.draw_pattern()
-    @piece.construct_piece("c")
+    @puzzle_pattern.draw_pattern()
+    @piece.construct_piece("d")
     @piece.draw_piece(0,0)

-#    @piece.draw_piece_ab(@piece.box.anchor_hex[0],@piece.box.anchor_hex[1])
-
-    @hex_box.set_hex_box("c")
+    @piece.draw_piece_ab(@piece.hex_box.anchor_hex[0],@piece.hex_box.anchor_hex[1])
+
+#    @hex_box.set_hex_box("j")

 #    @hex_draw.draw_all_hexes()

@@ -173,8 +173,8 @@
   constructor: (puzzle_app) ->

     @puzzle = puzzle_app
-    @pattern = @puzzle.pattern
-    @grid = @pattern.grid
+    @puzzle_pattern = @puzzle.puzzle_pattern
+    @grid = @puzzle_pattern.grid
     @hex_draw = @puzzle.hex_draw
     @missing = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"]

@@ -200,54 +200,65 @@
   constructor: (puzzle_app) ->

     @puzzle = puzzle_app
+    @hex_box = @puzzle.hex_box
+    @box = new PieceRenderBox(this)
+    @piece_mask  = new PiecePattern(this)
+    @redraw = new PieceRedrawBuffer
     @hexes = []
-
-    @grid_model = @puzzle.grid_model
-
-    @box = new PieceRenderBox(this)
-    @p_mask  = new PiecePattern(this)
-    @redraw = new PieceRedrawBuffer

   construct_piece: (sym) ->
     @sym = sym
     @hexes = @get_hexes()
+    @hex_box.set_hex_box(sym) # NEW
     @box.set_box_dimensions()
-    @p_mask.draw_pattern()
+    @piece_mask.draw_piece_pattern()
     @draw_piece(0,100)
     @cut_piece_from_photo()

+
   cut_piece_from_photo: () ->

     @photo_clip = document.createElement('canvas')
     @photo_clip.id = "photo-clip"
-    @photo_clip.width = @box.width
-    @photo_clip.height = @box.height
+    @photo_clip.width = @hex_box.width
+    @photo_clip.height = @hex_box.height
     @photo_clip_context = @photo_clip.getContext('2d')
-    @photo_clip_context.drawImage(@puzzle.puzzle_view.img,@box.box_xy[0],@box.box_xy[1],@box.width,@box.height,0,0,@box.width,@box.height)
+
+    xx = @hex_box.box_xy[0] - @puzzle.puzzle_view.puzzle_xy[0]
+    yy = @hex_box.box_xy[1] - @puzzle.puzzle_view.puzzle_xy[1]
+
+    @photo_clip_context.drawImage(@puzzle.puzzle_view.img,xx,yy,@hex_box.width,@hex_box.height,0,0,@hex_box.width,@hex_box.height)

     view_context = @puzzle.puzzle_view.context_canvas
     view_context.drawImage(@photo_clip,0,190)

-    context = @p_mask.piece_mask_context
+    context = @piece_mask.piece_mask_context
     context.globalCompositeOperation = 'source-atop'
     context.drawImage(@photo_clip,0,0)
     context.globalCompositeOperation = 'source-over'

-  draw_piece_ab: (a,b) ->
-
-    b = b-1 if a%2 == 0 and @box.anchor_hex[0] % 2 == 1
-    x = 107+a*14
-    y = 18+b*20
-    y = y+9 if @box.high_hex_adjust
-    y = y+9 if Math.abs(@box.anchor_hex[0] - a) % 2 == 1
-    @draw_piece(x,y)
+  # TODO Let the revised method use a new HexBox method that gives x,y values
+  # for rendering a piece at any arbitrary a,b.
+  draw_piece_ab: (a,b) -> #FIXME Right now, this actually draws the piece at its own a,b location.
+    xy = @puzzle.hex_box.get_box_xy()
+    @draw_piece(xy[0],xy[1])
+
+
+#  draw_piece_ab: (a,b) ->
+
+#    b = b-1 if a%2 == 0 and @box.anchor_hex[0] % 2 == 1
+#    x = 107+a*14
+#    y = 18+b*20
+#    y = y+9 if @box.high_hex_adjust
+#    y = y+9 if Math.abs(@box.anchor_hex[0] - a) % 2 == 1
+#    @draw_piece(x,y)

   draw_piece: (x,y) ->
     context = @puzzle.puzzle_view.context_canvas
-    context.drawImage(@p_mask.img,x,y)
+    context.drawImage(@piece_mask.img,x,y)

   # TODO Does this method belong in PuzzlePattern class?
@@ -255,7 +266,7 @@
     hexes = []
     for bb in [1..10]
       for aa in [1..24]
-        hexes.push([aa,bb]) if @puzzle.pattern.grid[bb][aa] == @sym
+        hexes.push([aa,bb]) if @puzzle.puzzle_pattern.grid[bb][aa] == @sym
     console.log("PIECE ID: "+@sym)
     console.log("    "+hx[0]+","+hx[1]) for hx in hexes
     hexes
@@ -278,18 +289,21 @@

   constructor: (piece) ->
     @piece = piece
-    @hex_draw = @piece.puzzle.hex_draw
+    @puzzle = @piece.puzzle
+    @hex_box = @puzzle.hex_box
+    @hex_draw = @puzzle.hex_draw
     @img = document.createElement('canvas')
     @img.id = "piece-mask"
     @piece_mask_context = @img.getContext('2d')

-  draw_pattern: () ->
-    @img.width = @piece.box.width
-    @img.height = @piece.box.height
+  draw_piece_pattern: () -> # TODO Fix this and the HexBox refactor is complete.
+    @img.width = @hex_box.width
+    @img.height = @hex_box.height
     @hexes = @piece.hexes
     @hex_draw.set_context("piece_mask")
     xx = 6
+    xx = 0
     if @piece.box.high_hex_adjust == true then hx_adjust = -9 else hx_adjust = 0
     for hx in @hexes
       aa = hx[0] - @piece.box.anchor_hex[0] + 1
@@ -306,7 +320,31 @@
       @fill_hex_ab_xy(aa,bb,xx,yy,3)

-  fill_hex_ab_xy: (a,b,x,y,c_no) ->
+###############################################################################
+#  draw_pattern: () -> # TODO Fix this and the HexBox refactor is complete.
+#    @img.width = @piece.box.width
+#    @img.height = @piece.box.height
+#    @hexes = @piece.hexes
+#    @hex_draw.set_context("piece_mask")
+#    xx = 6
+#    if @piece.box.high_hex_adjust == true then hx_adjust = -9 else hx_adjust = 0
+#    for hx in @hexes
+#      aa = hx[0] - @piece.box.anchor_hex[0] + 1
+#      bb = hx[1] - @piece.box.anchor_hex[1] + 1
+
+#      if @piece.box.anchor_hex[0] % 2 == 1
+#        yy = 9
+#      else
+#        if hx[0]%2 == 1
+#          yy = 18
+#        else
+#          yy = 0
+#      yy = yy + hx_adjust
+#      @fill_hex_ab_xy(aa,bb,xx,yy,3)
+###############################################################################
+
+
+  fill_hex_ab_xy: (a,b,x,y,c_no) -> # TODO Fix this and the HexBox refactor is complete.
     xx = x+(a-1)*14
     yy = y+(b-1)*20
     yy = yy-9 if (a%2 == 0)
@@ -368,6 +406,7 @@
   constructor: (piece) ->

     @piece = piece
+    @metrics_report = true

   set_box_dimensions: () ->
@@ -435,23 +474,24 @@
         console.log("Non-rendered anchor hex is out of bounds (higher than high hex).")
         @high_hex_adjust = true

-    console.log("PieceRenderBox:")
-    console.log("    top = "+top)
-    console.log("    anchor_top = "+anchor_top)
-    console.log("    high_hex_half_step = "+high_hex_half_step)
-    console.log("    bottom = "+bottom)
-    console.log("    left = "+left)
-    console.log("    right = "+right)
-    console.log("    center to anchor delta = "+@center_to_anchor_delta[0]+","+@center_to_anchor_delta[1])
-#    console.log("     = "+)
-#    console.log("     = "+)
-    console.log("    cols = "+cols)
-    console.log("    halves = "+halves)
-    console.log("    WIDTH = "+@width)
-    console.log("    HEIGHT = "+@height)
-    console.log("    BOX-XY = "+@box_xy[0]+","+@box_xy[1])
-    console.log("    HIGH HEX ADJUST = "+@high_hex_adjust)
-    console.log("    ANCHOR HEX = "+@anchor_hex[0]+","+@anchor_hex[1])
+    if @metrics_report == true
+      console.log("PieceRenderBox:")
+      console.log("    top = "+top)
+      console.log("    anchor_top = "+anchor_top)
+      console.log("    high_hex_half_step = "+high_hex_half_step)
+      console.log("    bottom = "+bottom)
+      console.log("    left = "+left)
+      console.log("    right = "+right)
+      console.log("    center to anchor delta = "+@center_to_anchor_delta[0]+","+@center_to_anchor_delta[1])
+  #    console.log("     = "+)
+  #    console.log("     = "+)
+      console.log("    cols = "+cols)
+      console.log("    halves = "+halves)
+      console.log("    WIDTH = "+@width)
+      console.log("    HEIGHT = "+@height)
+      console.log("    BOX-XY = "+@box_xy[0]+","+@box_xy[1])
+      console.log("    HIGH HEX ADJUST = "+@high_hex_adjust)
+      console.log("    ANCHOR HEX = "+@anchor_hex[0]+","+@anchor_hex[1])

@@ -482,7 +522,7 @@
       when "canvas" then context = @context_canvas
       when "piece_mask"
         if @puzzle.piece
-          context = @puzzle.piece.p_mask.img.getContext('2d')
+          context = @puzzle.piece.piece_mask.img.getContext('2d')
         else
           context = null
     return context
@@ -621,7 +661,6 @@

   get_box_metrics: () ->
-
     @init_box_params()
     for hx in @hexes
       aa = hx[0]
@@ -630,20 +669,13 @@
     @get_corner_fit()
     @get_anchor_hex()
     @get_box_xy()
+    @get_height_width()
     @report_metrics() if @metrics_report == true

-  report_metrics: () ->
-    console.log("HexBox: left = "+@left)
-    console.log("HexBox: right = "+@right)
-    console.log("HexBox: top = "+@top)
-    console.log("HexBox: bottom = "+@bottom)
-#    console.log("HexBox: ")
-    console.log("HexBox: corner fit = "+@corner_fit)
-    console.log("HexBox: anchor hex ="+@anchor_hex)
-    console.log("HexBox: box XY ="+@box_xy[0]+","+@box_xy[1])
-#    console.log("HexBox: ")
-#    console.log("HexBox: ")
+  get_height_width: () ->
+    @width = 14*(@right-@left+1) + 7
+    @height = 10*(@bottom-@top+2) + 1

   get_anchor_hex: () ->
@@ -691,8 +723,23 @@
     hexes = []
     for bb in [1..10]
       for aa in [1..24]
-        hexes.push([aa,bb]) if @puzzle.pattern.grid[bb][aa] == piece_symbol
+        hexes.push([aa,bb]) if @puzzle.puzzle_pattern.grid[bb][aa] == piece_symbol
     return hexes
+
+
+  report_metrics: () ->
+    console.log("HexBox: left = "+@left)
+    console.log("HexBox: right = "+@right)
+    console.log("HexBox: top = "+@top)
+    console.log("HexBox: bottom = "+@bottom)
+    console.log("HexBox: width = "+@width)
+    console.log("HexBox: height = "+@height)
+#    console.log("HexBox: ")
+    console.log("HexBox: corner fit = "+@corner_fit)
+    console.log("HexBox: anchor hex ="+@anchor_hex)
+    console.log("HexBox: box XY ="+@box_xy[0]+","+@box_xy[1])
+#    console.log("HexBox: ")
+#    console.log("HexBox: ")


set multiple pieces and complete the puzzle

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -1,34 +1,20 @@
 class PuzzleApp

   constructor: () ->
+    @pz_status = new PuzzleStatus(this)
+    @ui_status = new UiStatus(this)
+    @events = new EventHandler(this)
+
     @puzzle_view = new PuzzleView(this)
     @grid_model = new PuzzleGridModel(this)
     @hex_grid = new HexGrid(this)
     @hex_draw = new HexDraw(this)
-
     @hex_box = new HexBox(this)
-
-    @ui_status = new UiStatus(this)
-    @events = new EventHandler(this)
-
     @puzzle_pattern = new PuzzlePattern(this)
     @mask = new MissingPiecesMask(this)
     @piece = new PuzzlePiece(this)

-    @pz_status = new PuzzleStatus(this)
-
-
-    @mask.draw_mask()
-#    @puzzle_pattern.draw_pattern()
-#    @hex_draw.draw_all_hexes()
-#    @hex_draw.fill_all_hexes()
-
-    pieces = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"]
-    pc = Math.floor(16*Math.random())
-    @piece.construct_piece(pieces[pc])
-
-#    @pixel_test = new PixelHexTester(this)
-#    @pixel_test.adjusted_hex_quick_mark()
+    @pz_status.start_new_puzzle()

@@ -125,18 +111,38 @@

   constructor: (puzzle_app) ->
     @puzzle = puzzle_app
-    @reset_puzzle()
-
-
-  reset_puzzle: () ->
+    @all_pieces = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"]
+    @pieces_in_puzzle = 16
+
+
+  start_new_puzzle: () ->
+    @unset_pieces = @all_pieces
     @pieces_set = 0
+    @next_piece()

   set_piece: () ->
     @puzzle.ui_status.terminate_piece_drag()
     console.log("SET PIECE: "+@puzzle.piece.sym)
-    @pieces_set = @pieces_set + 1
+    @pieces_set += 1
+    @pieces_in_puzzle -= 1
     console.log(@pieces_set+" pieces are set")
+    if @pieces_set == 16
+      @puzzle_finished()
+    else
+      @next_piece()
+
+
+  puzzle_finished: () ->
+    alert("Puzzle finished")
+
+
+  next_piece: () ->
+    @puzzle.mask.draw_mask()
+    pc = Math.floor(@pieces_in_puzzle*Math.random())
+    @sym = @unset_pieces[pc]
+    @unset_pieces.splice(@unset_pieces.indexOf(@sym),1)
+    @puzzle.piece.construct_piece(@sym)

@@ -204,19 +210,21 @@

 class MissingPiecesMask
-  # TODO Add an Image object to this class. This way the puzzle piece image can
-  # move around the widget, being drawn over the mask image in 'source-atop"
-  # mode, so that the puzzle piece is only drawn over the mask image and nowhere
-  # else. Probably want to add the 'staging area' to the opaque part of the mask
-  # image, so that the piece can also be dragged across that region.
-
-  constructor: (puzzle_app) ->
-
+
+  constructor: (puzzle_app) ->
     @puzzle = puzzle_app
     @puzzle_pattern = @puzzle.puzzle_pattern
     @grid = @puzzle_pattern.grid
     @hex_draw = @puzzle.hex_draw
-    @missing = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"]
+    @init_missing_pieces_mask()
+
+
+#  TODO TODO TODO TODO
+#  Change this class to build the mask from the hexes for the pieces in
+#  @puzzle.pz_status.unset_pieces.
+
+  init_missing_pieces_mask: () ->
+    @missing = @puzzle.pz_status.all_pieces

   draw_mask: () ->


refactor additional references to puzzle piece grid

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -6,11 +6,11 @@
     @events = new EventHandler(this)

     @puzzle_view = new PuzzleView(this)
+    @hex_box = new HexBox(this)
     @piece = new PuzzlePiece(this)
     @colors = new ColorRotation
     @indicator = new Indicator(this)
     @grid_model = new PuzzleGridModel(this)
-    @hex_box = new HexBox(this)
     @hex_draw = new HexDraw(this)
     get_puzzle_pattern(this)

@@ -562,7 +562,7 @@
     hexes = []
     for bb in [1..10]
       for aa in [1..24]
-        hexes.push([aa,bb]) if @puzzle.puzzle_pattern.grid[bb][aa] == piece_symbol
+        hexes.push([aa,bb]) if @puzzle.grid[bb][aa] == piece_symbol
     return hexes


Correct test description

Correct test description

--- spec/python-tools-spec.coffee
+++ spec/python-tools-spec.coffee
@@ -115,7 +115,7 @@
             new Range(new Point(3, 6), new Point(3, 17)),
           ])

-    it "does alter current selection on no results", ->
+    it "doesn't alter current selection on no results", ->
       editor.setCursorBufferPosition(new Point(3, 2))
       waitsForPromise ->
         pythonTools.jediToolsRequest('usages').then ->


point

point

--- src/point.coffee
+++ src/point.coffee
@@ -241,21 +241,21 @@
     Scale this Point (Vector) by a constant amount. Modifies the point in place.

-       point = Point(5, 6)
-
-       point.x
-    => 5
-
-       point.y
-    => 6
-
-       point.scale$(2)
-
-       point.x
-    => 10
-
-       point.y
-    => 12
+    point = Point(5, 6)
+
+    point.x
+    # => 5
+
+    point.y
+    # => 6
+
+    point.scale$(2)
+
+    point.x
+    # => 10
+
+    point.y
+    # => 12

     @name scale$


fix text failure from last commit

fix text failure from last commit

--- src/quill.coffee
+++ src/quill.coffee
@@ -194,10 +194,14 @@

   setContents: (delta, source = Quill.sources.API) ->
     if Array.isArray(delta)
-      delta = { ops: delta.slice() }
+      delta = new Delta(delta.slice())
     else
-      delta = { ops: delta.ops.slice() }
-    delta.ops.push({ delete: this.getLength() - 1 })
+      delta = new Delta(delta.ops.slice())
+    # Retain trailing newline unless inserting one
+    lastOp = _.last(delta.slice(delta.length() - 1).ops)
+    delta.delete(this.getLength() - 1)
+    if _.isString(lastOp.insert) and _.last(lastOp.insert) == '\n'
+      delta.delete(1)
     this.updateContents(delta, source)

   setHTML: (html, source = Quill.sources.API) ->


Using old temporary variable name generation for single-letter types (e.g., i, j, k, and so on).

--- src/scope.coffee
+++ src/scope.coffee
@@ -68,7 +68,10 @@

   # Generate a temporary variable name at the given index.
   temporary: (type, index) ->
-    '_' + type + if index > 1 then index else ''
+    if type.length > 1
+      '_' + type + if index > 1 then index else ''
+    else
+      '_' + (index + parseInt type, 36).toString(36).replace /\d/g, 'a'

   # If we need to store an intermediate result, find an available name for a
   # compiler-generated variable. `_var`, `_var2`, and so on...


Show levels,bg with undefined page number on first campaign page (#161)

--- app/views/play/CampaignView.ozar.coffee
+++ app/views/play/CampaignView.ozar.coffee
@@ -862,8 +862,8 @@
   applyCampaignStyles: ->
     return unless @campaign?.loaded
     if (backgrounds = @campaign.get 'backgroundImage') and backgrounds.length
-      if backgrounds.filter((b) => b.pageNumber).length == 0
-        background = backgrounds[0]
+      if @campaignPage == 1
+        background = backgrounds.find((b) => !b.pageNumber || b.pageNumber == @campaignPage) || {}
       else
         background = backgrounds.find((b) => b.pageNumber == @campaignPage) || {}
       rule = "#campaign-view .map-background { background-image: url(/file/#{background.image}); }"
@@ -1349,8 +1349,10 @@
     return @courseLevelsFake if @courseLevels?
     currentLevels = @campaign?.get('levels')
     if currentLevels
-      # Show levels without page number / having same page number as in url
-      currentLevelsKeys = Object.keys(currentLevels).filter((l) => !currentLevels[l].pageNumber || currentLevels[l].pageNumber == @campaignPage)
+      if @campaignPage == 1
+        currentLevelsKeys = Object.keys(currentLevels).filter((l) => !currentLevels[l].pageNumber || currentLevels[l].pageNumber == @campaignPage)
+      else
+        currentLevelsKeys = Object.keys(currentLevels).filter((l) => currentLevels[l].pageNumber == @campaignPage)
       return _.pick(currentLevels, currentLevelsKeys)
     return currentLevels


hopefully defer will fix all our filter problems

--- apps/fair/components/browse/view.coffee
+++ apps/fair/components/browse/view.coffee
@@ -41,9 +41,9 @@
         @fair.trigger 'countsFetched'

         # For initializing fair artwork filtering on pageload
-        @triggerArtworks = _.once => @artworkParams.trigger 'reset'
         @filterArtworksView.router.on 'artworks-route', =>
-          @triggerArtworks()
+          _.defer => @artworkParams.trigger 'reset'
+          @filterArtworksView.router.off 'artworks-route'

         Backbone.history.start pushState: true


Added a female ogre name (#4172)

Added a female ogre name (#4172)

* Added female over name

added babaga as a name

* Placed babaga in appropriate spot

Name is now alphabetical order

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -3,6 +3,7 @@
     # Female
     'Alali'
     'Anabel'
+    'Babaga'
     'Delma'
     'Dosha'
     'Gurzunn'


leaner code

leaner code

--- src/art/engine/core/element.coffee
+++ src/art/engine/core/element.coffee
@@ -542,8 +542,8 @@
     absoluteAxis:         (pending) -> state = @getState(pending); state._currentSize.mul state._axis

     sizeForChildren: (pending) ->
-      state = @getState pending
-      state._currentPadding.subtractedFromSize state._currentSize
+      {_currentPadding, _currentSize} = @getState pending
+      _currentPadding.subtractedFromSize _currentSize

     parentSize: -> throw new Error "parentSize depricated"


visual mode default keybinding: delete with x

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -13,6 +13,7 @@
   'w': 'vim-mode:move-to-next-word'
   'e': 'vim-mode:move-to-end-of-word'
   'b': 'vim-mode:move-to-previous-word'
+  'x': 'vim-mode:delete'
   'd': 'vim-mode:delete'
   'c': 'vim-mode:change'
   'y': 'vim-mode:yank'


SocialWorker: JAccount: add fetchMySessions method

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -121,6 +121,8 @@
         fetchMyPermissions:
           (signature Function)
         fetchMyPermissionsAndRoles:
+          (signature Function)
+        fetchMySessions:
           (signature Function)
         blockUser: [
           (signature String, Number, Function)
@@ -1407,3 +1409,12 @@

       user.update { $set: { twofactorkey: key } }, (err) ->
         callback err
+
+
+  fetchMySessions: secure (client, callback) ->
+
+    JSession = require './session'
+    username = @profile.nickname
+    JSession.some { username }, { limit : 10 }, callback
+
+


Register via Cookie session ID

Register via Cookie session ID

--- client/app/MainApp/kite/newkite.coffee
+++ client/app/MainApp/kite/newkite.coffee
@@ -4,10 +4,34 @@
     super
     @readyState = false
     @localStore = {}
-    @websocket = @createWebSocket()
-    @registerEvents()
+    @getKiteAddr()

-  createWebSocket:-> new WebSocket "ws://127.0.0.1:4000/sock"
+  createWebSocket: (url) ->
+    new WebSocket "ws://#{url}/sock"
+
+  getKiteAddr:()->
+    requestData =
+      name       : "#{KD.nick()}"
+      remoteKite : @kiteName
+      token      : KD.remote.getSessionToken()
+
+    $.ajax
+     type    : "POST"
+     url     : "http://127.0.0.1:4000/request" #kontrol addr
+     data    : JSON.stringify requestData
+     success: (data, status, response) =>
+       if response.status is 200
+         data = JSON.parse data
+
+         console.log "Remote Kite is #{data[0].name}"
+         console.log "Addr to be connected is #{data[0].addr}"
+         console.log "Token to use is #{data[0].token}"
+
+         @websocket = @createWebSocket(data[0].addr)
+         @registerEvents()
+
+     error: (data, status, response) ->
+       console.log "error kontrol kite request", data, status, response

   registerEvents:()->
     @websocket.onopen = (evt) => @onOpen evt
@@ -40,8 +64,9 @@
     @once 'ready', callback

   call : (methodName, rest..., callback)->
+    if not KD.isLoggedIn()
+      return

-    # create a unique id
     id = Bongo.createId 12

     request =


[app][main] removed stripTemplate method

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -55,13 +55,6 @@
       @emit 'GroupChanged', group
       appManager.tell app, 'setGroup', group
     appManager.tell app, 'handleQuery', query
-
-  stripTemplate =(str, konstructor)->
-    {slugTemplate} = konstructor
-    slugStripPattern = /^(.+)?(#\{slug\})(.+)?$/
-    re = RegExp slugTemplate.replace slugStripPattern,
-      (tmp, begin, slug, end)-> "^#{begin ? ''}(.*)#{end ? ''}$"
-    str.match(re)?[1]

   handleNotFound:(route)->


JAccount: send update events when there is a modification in account [Fixes #76425662]

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -365,6 +365,10 @@
     @notifyOriginWhen 'PrivateMessageSent', 'FollowHappened'
     @notifyGroupWhen 'FollowHappened'

+  @sendUpdateInstanceEvent = (obj, op) =>
+    JAccount.bongos.forEach (bongo) =>
+      bongo.handleEvent "instance", obj, "updateInstance", [op]
+
   canEditPost: permit 'edit posts'

   canDeletePost: permit 'delete posts'
@@ -772,7 +776,8 @@
           console.error 'Could not update user exempt information'
           return callback err
         @isExempt = exempt
-        JAccount.bongos.forEach (bongo) => bongo.handleEvent "instance", @, "updateInstance", [op]
+
+        JAccount.sendUpdateInstanceEvent this, op
         callback null, result

   markUserAsExemptInSocialAPI: (client, exempt, callback)->
@@ -941,7 +946,11 @@
         return callback new KodingError "Modify fields is not valid"

     if @equals(client.connection.delegate)
-      @update $set: fields, callback
+      op = $set: fields
+      @update op, (err) =>
+        JAccount.sendUpdateInstanceEvent this, op  unless err
+
+        return callback err

   setClientId:(@clientId)->


Set forced recipient as default user identifier

--- workers/social/lib/social/models/tracker.coffee
+++ workers/social/lib/social/models/tracker.coffee
@@ -115,6 +115,8 @@

     return  unless KONFIG.sendEventsToSegment

+    userId = KONFIG.forcedRecipient or userId
+
     options = { userId, name, category, properties }
     @addDefaults options
     analytics.page options
@@ -123,6 +125,8 @@
   @alias = (previousId, userId) ->

     return  unless KONFIG.sendEventsToSegment
+
+    userId = KONFIG.forcedRecipient or userId

     options = { previousId, userId }
     @addDefaults options


Add comments && rename var

Add comments && rename var

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -589,6 +589,7 @@

         messageID = param.messageID or @getCurrentID()
         conversationID = param.conversationID or @getByID(messageID)?.get 'conversationID'
+
         messages = _messagesWithInFlights()

         # Remove selected messages
@@ -607,17 +608,21 @@
             messages = _currentMessages

         getMessage = =>
-            _newMessage = (index) ->
+            _getMessage = (index) ->
                 index0 = param.transform index
                 messages?.get(index0)

-            index0 = messages.toArray().findIndex (message, index) ->
-                if conversationID is message?.get 'conversationID'
-                    if _newMessage(index)?.get('conversationID') isnt conversationID
-                        return true
-
-            _newMessage index0
-
+            # Get next Conversation not next message
+            # `messages` is the list of all messages not conversations
+            # TODO : regroup message by its conversationID
+            # and use messages.find instead with a simple test
+            # FIXME : inconsistency between the 2 results, see why?
+            index0 = messages.findIndex (message, index) ->
+                isSameMessage = conversationID is message?.get 'conversationID'
+                isNextSameConversation = _getMessage(index)?.get('conversationID') isnt conversationID
+                return isSameMessage and isNextSameConversation
+
+            _getMessage(index0)

         # Change Conversation
         return Immutable.Map getMessage()


Popover coffeescript optimized.

Popover coffeescript optimized.

--- webapp/public/pbsfinder/javascripts/jobs/table_header.coffee
+++ webapp/public/pbsfinder/javascripts/jobs/table_header.coffee
@@ -4,6 +4,7 @@
   s = 8
   t = $('#protein-table')
   c = t.find('tr').size() - 1
+  show_popover = false

   # Auxiliary methods.
   isFootableNeeded = () ->
@@ -25,9 +26,11 @@
       nextText: "Next →",
       lastText: "Last ⇒"
     }).bind({'footable_sorting': (e) ->
-      $('[data-toggle="popover"]').each(() ->
-        if (!$(this).is(e.target) && $(this).has(e.target).length == 0 && $('.popover').has(e.target).length == 0)
-          $(this).popover('hide')
+      if show_popover
+        show_popover = false
+        $('[data-toggle="popover"]').each(() ->
+          if (!$(this).is(e.target) && $(this).has(e.target).length == 0 && $('.popover').has(e.target).length == 0)
+            $(this).popover('hide')
     )
     })

@@ -48,19 +51,27 @@
           title: 'Cluster ' + cls,
           html: true,
           content: cont.html()
-        }).on("show.bs.popover", -> $(this).data("bs.popover").tip().css(maxWidth: "600px"))
+        }).on("show.bs.popover", ->
+          $(this).data("bs.popover").tip().css(maxWidth: "600px")
+          show_popover = true
+        )

   # Disable multiple popovers.
   $('html').on('click', (e) ->
-    $('[data-toggle="popover"]').each(() ->
-      if (!$(this).is(e.target) && $(this).has(e.target).length == 0 && $('.popover').has(e.target).length == 0)
+    if show_popover == true
+      $('[data-toggle="popover"]').each(() ->
+        if (!$(this).is(e.target) && $(this).has(e.target).length == 0 && $('.popover').has(e.target).length == 0)
+          $(this).popover('hide')
+      )
+  )
+
+  # Disable popovers on scroll.
+  $('.table-container').scroll( ->
+    if show_popover == true
+      show_popover = false
+      $('[data-toggle="popover"]').each(() ->
         $(this).popover('hide')
-    )
-  )
-  $('.table-container').scroll( ->
-    $('[data-toggle="popover"]').each(() ->
-      $(this).popover('hide')
-    )
+      )
   )

   # Enable header tooltips.


Added persistence for ID caching

Added persistence for ID caching

--- src/steam.coffee
+++ src/steam.coffee
@@ -29,7 +29,6 @@
 #
 STEAM_API_KEY = process.env.STEAM_API_KEY
 STEAM_API_URL = "https://api.steampowered.com"
-DOTA_HEROES_DATA_PATH = "../data/heroes.bin"
 DOTA_MAX_RESULTS = process.env.DOTA_MAX_RESULTS or 5

 #
@@ -70,6 +69,13 @@
 DOTA_HEROES = {}

 #
+# Brain / Persistence
+#
+_brain = null
+_GetSteamData = ->
+    _brain.data.steam or= {}
+
+#
 # Steam API
 #
 GetSteamResult = (msg, endpoint, params = {}, handler, version = 1) ->
@@ -89,16 +95,33 @@
 # User API
 #
 GetSteamID = (msg, customURL, callback) ->
+    # Retrieve from cache
+    return callback steamID for steamID, user of _GetSteamData() when user.url is customURL
+
+    # Retrieve from API
     GetSteamResult msg, "ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->
         if object.response.success is 42
             msg.reply "The custom URL you have entered (\"#{customURL}\") does not exist."
             return
+
+        # Cache results
+        _GetSteamData()[object.response.steamid] or= {}
+        _GetSteamData()[object.response.steamid].url = customURL
         callback object.response.steamid

 _GetCommunityID = (steamID) ->
+    # Retrieve from cache
+    if _GetSteamData()[steamID]?.cID?
+        return _GetSteamData()[steamID].cID
+
+    # Retrieve through calculating
     buffer = new Buffer 8
     buffer.writeUInt64LE steamID, 0
     communityID = buffer.readUInt32LE 0
+
+    # Cache results
+    _GetSteamData()[steamID] or= {}
+    _GetSteamData()[steamID].cID = communityID
     communityID

 GetPlayerSummaries = (msg, genericID, callback) ->
@@ -177,6 +200,10 @@
     GetHeroes robot, (heroes) ->
         DOTA_HEROES = heroes

+    # TODO: Cleaner dependency
+    robot.brain.resetSaveInterval 1800
+    _brain = robot.brain
+
 _PossessionModifier = (noun) ->
     noun += if noun.slice(-1) is "s" then "'" else "'s"


Client: Only create chatView once.

Client: Only create chatView once.

--- client/app/js/client.coffee
+++ client/app/js/client.coffee
@@ -47,11 +47,6 @@
 PokeBattle.socket = new Socket(new SockJS('/socket'))
 PokeBattle.socket.addEvents
   'connect': (socket) ->
-    PokeBattle.chatView = new ChatView(
-      el: $('.chat_window .chat')
-      collection: PokeBattle.userList
-    )
-    PokeBattle.chatView.render()
     if !PokeBattle.ready  # Only trigger this event once.
       PokeBattle.ready = true
       PokeBattle.events.trigger("ready")
@@ -82,4 +77,8 @@
   PokeBattle.navigation = new SidebarView(el: $('#navigation'))
   PokeBattle.teambuilder = new TeambuilderView(el: $("#teambuilder-section"), teams: [])
   PokeBattle.battleList = new BattleListView(el: $("#battle-list-section"))
+  PokeBattle.chatView = new ChatView(
+    el: $('.chat_window .chat')
+    collection: PokeBattle.userList
+  ).render()
   new PrivateMessagesView(el: $("#messages"), collection: PokeBattle.messages)


Add parens for clarity

Add parens for clarity

--- src/token.coffee
+++ src/token.coffee
@@ -118,7 +118,7 @@
     @buildTabToken(tabLength, false, 0)

   buildTabToken: (tabLength, isHardTab, column=0) ->
-    tabStop = tabLength - column % tabLength
+    tabStop = tabLength - (column % tabLength)
     new Token(
       value: _.multiplyString(" ", tabStop)
       scopes: @scopes


Blur min/max input on enter

Blur min/max input on enter

[#99423710]

--- src/code/views/node-value-inspector-view.coffee
+++ src/code/views/node-value-inspector-view.coffee
@@ -44,6 +44,10 @@
       if value
         @props.graphStore.changeNodeProperty property, value

+    keyDown = (evt) ->
+      if evt.key is 'Enter'
+        swapState()
+
     if not @state["editing-#{property}"]
       (div {className: "half small editable-prop #{classNames}", onClick: swapState}, @props.node[property])
     else
@@ -53,6 +57,7 @@
         value: @props.node[property]
         onChange: updateProperty
         onBlur: swapState
+        onKeyDown: keyDown
         ref: 'focusable'}
       )


trigger load when results may change

trigger load when results may change

--- client/lanes/models/Query.coffee
+++ client/lanes/models/Query.coffee
@@ -223,6 +223,9 @@
             @results.reset()
             @results.ensureLoaded() if this.autoRetrieve
         )
+        this.on('change:src change:results', ->
+            this.trigger('load')
+        )
         if @initialFieldIndex
             @initialField = this.fields.at(@initialFieldIndex)
         @initialField ||= this.fields.findWhere(id: "code") ||


sort of duplicate original contentText logic

--- resources/assets/coffee/react/modding-profile/events.coffee
+++ resources/assets/coffee/react/modding-profile/events.coffee
@@ -19,6 +19,7 @@
                 if !event.beatmapset
                   continue

+                console.log event
                 cover = if event.beatmapset then event.beatmapset.covers.list else ''
                 discussionId = event.comment?.beatmap_discussion_id ? null
                 discussionLink = laroute.route('beatmapsets.discussion', beatmapset: event.beatmapset.id)
@@ -36,10 +37,13 @@
                       div
                         className: "beatmapset-event__content"
                         dangerouslySetInnerHTML:
-                          __html: osu.trans "beatmapset_events.event.#{@typeForTranslation(event)}",
-                            user: @props.users[event.user_id].username
-                            discussion: (if discussionId then "##{discussionId}" else '')
-                            text: (if event.discussion?.starting_post? then _.truncate(event.discussion.starting_post.message, {length: 100}) else '[no preview]')
+                          __html: @contentText(event, @props.users, discussionId)
+                          # __html: osu.trans "beatmapset_events.event.#{@typeForTranslation(event)}",
+                          #   user: @props.users[event.user_id].username
+                          #   discussion: (if discussionId then "##{discussionId}" else '')
+                          #   text: (if event.discussion?.starting_post? then _.truncate(event.discussion.starting_post.message, {length: 100}) else '[no preview]')
+                          #   old: event.comment?.old
+                          #   new: event.comment?.new

                       div
                         className: 'beatmap-discussion-post__info'
@@ -59,3 +63,50 @@
       'disqualify_legacy'

     event.type
+
+
+  contentText: (event, users, discussionId, discussions = null) =>
+    if discussionId
+      if discussions?
+        discussion = discussions[discussionId]
+
+        if discussion?
+          url = BeatmapDiscussionHelper.url discussion: discussion
+          text = BeatmapDiscussionHelper.previewMessage(discussion.posts[0]?.message)
+        else
+          url = laroute.route 'beatmap-discussions.show', beatmap_discussion: discussionId
+          text = osu.trans('beatmapset_events.item.discussion_deleted')
+
+        discussionLink = osu.link(url, "##{discussionId}", classNames: ['js-beatmap-discussion--jump'])
+      else
+        discussionLink = osu.link(laroute.route('beatmapsets.discussion', beatmapset: event.beatmapset.id), "##{discussionId}", classNames: ['js-beatmap-discussion--jump'])
+
+    else if event.type == 'discussion_lock'
+      text = BeatmapDiscussionHelper.format event.comment.reason, newlines: false
+    else if event.type not in ['genre_edit', 'language_edit']
+      text = BeatmapDiscussionHelper.format event.comment, newlines: false
+
+    if event.user_id?
+      user = osu.link(laroute.route('users.show', user: event.user_id), users[event.user_id].username)
+
+    key =
+      if event.type == 'disqualify'
+        if discussionId?
+          'disqualify'
+        else
+          'disqualify_legacy'
+      else
+        event.type
+
+    message = osu.trans "beatmapset_events.event.#{key}",
+      discussion: discussionLink
+      old: event.comment?.old
+      new: event.comment?.new
+      text: text
+      user: user
+
+    # append owner of the event if not already included in main message
+    if user? && event.type not in ['disqualify', 'kudosu_gain', 'kudosu_lost', 'nominate']
+      message += " (#{user})"
+
+    message


take out disabled review controls

take out disabled review controls

--- src/components/task-step/concept-coach-end.cjsx
+++ src/components/task-step/concept-coach-end.cjsx
@@ -5,23 +5,6 @@
 {TaskStore} = require '../../flux/task'
 {CardBody, PinnableFooter, ChapterSectionMixin} = require 'openstax-react-components'
 Review = require '../task/review'
-
-# TODO: will no longer need this when we move coach into this repo.
-ConceptCoachReviewControls = React.createClass
-  displayName: 'ConceptCoachReviewControls'
-  mixins: [ ChapterSectionMixin ]
-  render: ->
-    {taskId} = @props
-    details = TaskStore.getDetails(taskId)
-    current = _.map details.sections, @sectionFormat
-
-    <BS.ButtonGroup justified className='concept-coach-task-review-controls'>
-      <BS.Button disabled bsSize='large'>
-        Return to {current}</BS.Button>
-      <BS.Button disabled bsSize='large' bsStyle='primary'>
-        Continue to …</BS.Button>
-    </BS.ButtonGroup>
-

 ConceptCoachEnd = React.createClass
   displayName: 'TaskEnd'
@@ -46,37 +29,17 @@
     incompleteSteps = TaskStore.getIncompleteSteps taskId
     _.isEmpty(incompleteSteps)

-  renderStatusMessage: ->
-    {taskId} = @props
-
-    doneMessage = 'You\'re done.'
-    <CardBody className='completed-message'>
-

-
{doneMessage}

-

-      <ConceptCoachReviewControls taskId={taskId}/>
-
or review your work below.

-    </CardBody>
-
-  renderBottomControl: ->
-    {taskId} = @props
-
-    <CardBody className='completed-message'>
-      <ConceptCoachReviewControls taskId={taskId}/>
-    </CardBody>
-
   render: ->
     {footer, taskId} = @props
     completedSteps = TaskStore.getCompletedSteps taskId
     completedReview = @renderReviewSteps(taskId, completedSteps)

-      {@renderStatusMessage() if @isDone()}
+
Summary

       {completedReview}
-      {@renderBottomControl() if @isDone()}
       <PinnableFooter>
         {footer}
       </PinnableFooter>

-module.exports = {ConceptCoachReviewControls, ConceptCoachEnd}
+module.exports = {ConceptCoachEnd}


Not sure how void 0 got in there...

Not sure how void 0 got in there...

--- example.coffee
+++ example.coffee
@@ -22,5 +22,5 @@

 {docopt} = require('docopt')

-options = docopt(doc, void 0, true, '1.0.0')  # parse options based on doc above
+options = docopt(doc, null, true, '1.0.0')  # parse options based on doc above


tidied container a little

tidied container a little

--- lib/assets/javascripts/brancusi/container/container.js.coffee
+++ lib/assets/javascripts/brancusi/container/container.js.coffee
@@ -4,130 +4,136 @@
 #
 class brancusi.Container

-    # Creates an instance of a container.
-    #
-    # @param parent [brancusi.Container] a parent container. When provided, if resolution of a dependency fails then resolution will be attempted with the parent container instead.
-    #
-    constructor: (@parent) ->
-        @_mappings = {}
-
-    # @private
-    #
-    _register_mapping: (name, ref, kind, opts = {}) ->
-        @_mappings[name] = _.defaults( { kind: kind, ref: ref }, opts )
+  # Creates an instance of a container.
+  #
+  # @param parent [brancusi.Container] a parent container. When provided, if resolution of a dependency fails then resolution will be attempted with the parent container instead.
+  #
+  constructor: (@parent) ->
+    @_mappings = {}
+
+  # Registers a class mapping with the container.
+  #
+  # @param name [String] the name of the mapping to the class.
+  # @param klass [Class] the class the dependency should resolve with.
+  # @option opts [Boolean] singleton indicates whether the resolved dependency should be memoized.
+  # @return [Container] the container.
+  #
+  register_class: (name, klass, opts = {}) ->
+    @_register_mapping name, klass, "class", opts
+    @
+
+
+  # Registers an instance mapping with the container.
+  #
+  # @param name [String] the name of the mapping to the instance.
+  # @param obj [Object] the instance the dependency should resolve to.
+  # @return [Container] the container.
+  #
+  register_instance: (name, obj) ->
+    @_register_mapping name, obj, "instance"
+    @
+

-    # @private
-    #
-    _resolve_dependencies: (target, properties) ->
-
-    # Registers a class mapping with the container.
-    #
-    # @param name [String] the name of the mapping to the class.
-    # @param klass [Class] the class the dependency should resolve with.
-    # @option opts [Boolean] singleton indicates whether the resolved dependency should be memoized.
-    # @return [Container] the container.
-    #
-    register_class: (name, klass, opts = {}) ->
-        @_register_mapping name, klass, "class", opts
-        @
-
-
-    # Registers an instance mapping with the container.
-    #
-    # @param name [String] the name of the mapping to the instance.
-    # @param obj [Object] the instance the dependency should resolve to.
-    # @return [Container] the container.
-    #
-    register_instance: (name, obj) ->
-        @_register_mapping name, obj, "instance"
-        @
-
-
-    # Registers a factory function for resolving dependencies
-    #
-    # @param name [String] the name of the mapping
-    # @param fn [Function] the factory function to resolve the mapping
-    # @return [Container] the container
-    #
-    register_factory: (name, fn) ->
-        @_register_mapping name, fn, "factory"
-        @
+  # Registers a factory function for resolving dependencies
+  #
+  # @param name [String] the name of the mapping
+  # @param fn [Function] the factory function to resolve the mapping
+  # @return [Container] the container
+  #
+  register_factory: (name, fn) ->
+    @_register_mapping name, fn, "factory"
+    @

-    # Resolves a dependency by name.
-    #
-    # @private
-    # @param name [String] the name of the dependency.
-    #
-    _resolve_string: ( name, opts ) ->
-        mapping = @_mappings[name]
-        return null unless mapping?
-
-        if mapping.kind == "instance"
-            mapping.ref
-        else if mapping.kind == "class" and mapping.singleton == true
-            mapping.instance = @resolve( mapping.ref, opts ) unless mapping.instance?
-            mapping.instance
-        else if mapping.kind == "factory"
-            mapping.ref.apply(null, opts)
-        else
-            @resolve( mapping.ref, opts )
+  # Returns an instance of the given dependency, resolving any child dependencies.
+  #
+  # @overLoad resolve(name)
+  #   Resolves the dependency according to the name of the mapping.
+  #   @param name [String] the name of the dependency mapping.
+  #   @return [Object] the fully resolved dependency.
+  #
+  # @overLoad resolve(target, opts)
+  #   Resolves any unresolved dependencies on a given object.
+  #   @param target [Object] the object to resolve dependencies for.
+  #   @return [Object] target.
+  #
+  resolve: (ref, opts) ->
+    if typeof ref == "string"
+      resolution = @_resolve_string(ref, opts)
+    else if typeof ref == "function"
+      resolution = @_resolve_function(ref, opts)
+    else if typeof ref == "object"
+      resolution = @_resolve_object(ref)
+
+    if resolution?
+      resolution
+    else if @parent?
+      @parent.resolve ref, opts unless resolution?
+    else
+      throw new Error("Unable to resolve dependency: #{ref}") unless resolution?
+
+
+  # Creates a child container.
+  # @return [Container] a new child container.
+  #
+  child: ->
+    new Container(@)

-    # Resolves a dependency specified by a function.
-    #
-    # @private
-    # @param
-    #
-    _resolve_function: ( fn, opts ) ->
-        opts ?= []
-        obj = new fn( opts... )
-        @resolve obj
-
-    # Resolves dependencies on the given object.
-    #
-    # @private
-    # @param target [Object] the target object.
-    #
-    _resolve_object: ( target ) ->
-        for name, args of target.dependencies
-            [dependency, dependency_args] = args
-            if typeof dependency == "function"
-                target[name] = dependency.apply(target, [@])
-            else
-                target[name] = @resolve dependency, dependency_args
-        target
+  # Helper method to set up a mapping.  Merges in the given options to the definition.
+  #
+  # @private
+  # @param name [String] the name of the mapping
+  # @param [Class|Object|Function] the dependency to map too
+  # @param [String] the kind of mapping.  Either 'class', 'object' or 'factory'
+  #
+  _register_mapping: (name, ref, kind, opts = {}) ->
+    @_mappings[name] = _.defaults({ kind: kind, ref: ref }, opts)
+
+
+  # Resolves a dependency by name, passing the given options (if provided) to the dependency when initialized.
+  #
+  # @private
+  # @param name [String] the name of the dependency.
+  # @param opts [Array] optional array of arguments to pass to the dependency
+  #
+  _resolve_string: (name, opts) ->
+    mapping = @_mappings[name]
+    return null unless mapping?

-    # Returns an instance of the given dependency, resolving any child dependencies.
-    #
-    # @overLoad resolve(name)
-    #   Resolves the dependency according to the name of the mapping.
-    #   @param name [String] the name of the dependency mapping.
-    #   @return [Object] the fully resolved dependency.
-    #
-    # @overLoad resolve(target, opts)
-    #   Resolves any unresolved dependencies on a given object.
-    #   @param target [Object] the object to resolve dependencies for.
-    #   @return [Object] target.
-    #
-    resolve: (ref, opts) ->
-        if typeof ref == "string"
-            resolution = @_resolve_string( ref, opts )
-        else if typeof ref == "function"
-            resolution = @_resolve_function( ref, opts )
-        else if typeof ref == "object"
-            resolution = @_resolve_object( ref )
-
-        if resolution?
-            resolution
-        else if @parent?
-            @parent.resolve ref, opts unless resolution?
-        else
-            throw new Error( "Unable to resolve dependency: #{ref}" ) unless resolution?
-
-    # Creates a child container.
-    # @return [Container] a new child container.
-    #
-    child: ->
-        new Container( @ )
-        +    if mapping.kind == "instance"
+      mapping.ref
+    else if mapping.kind == "class" and mapping.singleton == true
+      mapping.instance = @resolve( mapping.ref, opts ) unless mapping.instance?
+      mapping.instance
+    else if mapping.kind == "factory"
+      mapping.ref.apply(null, opts)
+    else
+      @resolve(mapping.ref, opts)
+
+
+  # Resolves a dependency specified by a function.
+  #
+  # @private
+  # @param fn [Function] the function to invoke
+  # @param opts [Array] an optional array of arguments to pass to the function
+  #
+  _resolve_function: (fn, opts) ->
+    opts ?= []
+    obj = new fn(opts...)
+    @resolve obj
+
+
+  # Resolves dependencies on the given object.
+  #
+  # @private
+  # @param target [Object] the target object.
+  #
+  _resolve_object: (target) ->
+    for name, args of target.dependencies
+      [dependency, dependency_args] = args
+      if typeof dependency == "function"
+        target[name] = dependency.apply(target, [@])
+      else
+        target[name] = @resolve dependency, dependency_args
+    target


调整source顺序(恢复)

调整source顺序(恢复)

--- sources.coffee
+++ sources.coffee
@@ -51,7 +51,6 @@
 			"src/widget/base/reveal.coffee"
 			"src/widget/base/search.coffee"
 			"src/widget/base/toolbar.coffee"
-			"src/widget/base/notify-tip.coffee"
 		]
 		edit: [
 			"src/widget/edit/abstract-editor.coffee"
@@ -78,6 +77,7 @@
 			"src/widget/layout/sidebar.coffee"
 			"src/widget/layout/tab.coffee"
 			"src/widget/layout/panel.coffee"
+			"src/widget/base/notify-tip.coffee"
 			"src/widget/layout/split-pane.coffee"
 		]
 		collection: [
@@ -149,8 +149,8 @@
 			"src/css/sidebar.less"
 			"src/css/time-line.less"
 			"src/css/panel.less"
-#			"src/css/semantic/input-overrides.less"
-#			"src/css/semantic/segment-overrides.less"
+			# "src/css/semantic/input-overrides.less"
+			# "src/css/semantic/segment-overrides.less"
 			"src/css/textarea.less"
 			"src/css/notify-tip.less"
 			"src/css/split-pane.less"
@@ -176,7 +176,7 @@
 			"src/lib/scroller.js"
 			"src/lib/easy-scroller.js"

-#			"src/lib/fastclick.js"
+			# "src/lib/fastclick.js"
 		]

 	"lib-core":
@@ -191,6 +191,6 @@
 		css: ["lib/semantic-ui/semantic.css"]

 sources.coffee.widgetAll = _.union(sources.coffee.widget, sources.coffee.base,
-	sources.coffee.layout, sources.coffee.edit, sources.coffee.collection, sources.coffee.list)
+  sources.coffee.layout, sources.coffee.edit, sources.coffee.collection, sources.coffee.list)

 module?.exports = sources


Added CPU initialization base code.

Added CPU initialization base code.

--- src/CPU.coffee
+++ src/CPU.coffee
@@ -1,23 +1,43 @@
+AddressingMode =
+	ZeroPage: 1
+	// TODO add all addressing modes
+
 Instruction =
 	ADC: 1
 	// TODO add all instructions

-AddressingMode =
-	ZeroPage: 1
-	// TODO add all addressing modes
-
 class CPU
 	constructor(@memory): ->
+		@initAddressingModesTable()
 		@initInstructionsTable()
+		@initOperationsTable()
 		@reset()
+
+	initAddressingModesTable: ->
+		@addressingModesTable = []
+		@registerAddressingMode AddressingMode.ZeroPage, () ->
+			// TODO addressing mode code
+		// TODO add all addressing modes
+
+	registerAddressingMode: (addressingMode, computation) ->
+		@addressingModesTable[addressingMode] = computation

 	initInstructionsTable: ->
 		@instructionsTable = []
-		@registerInstruction 0x65 Instruction.ADC, AddressingMode.ZeroPage, 2, 2
-		// TODO add instructions for all operations codes
+		@registerInstruction Instruction.ADC, (address) ->
+			// TODO instruction code
+		// TODO add all instructions

-	registerInstruction: (operationCode, instruction, addressingMode, size, cycles) ->
-		@instructionsTable[operationCode] =
+	registerInstruction: (instruction, execution) ->
+		@instructionsTable[instruction] = execution
+
+	initOperationsTable: ->
+		@operationsTable = []
+		@registerOperation 0x65 Instruction.ADC, AddressingMode.ZeroPage, 2, 2
+		// TODO add all operations
+
+	registerOperation: (operationCode, instruction, addressingMode, size, cycles) ->
+		@operationsTable[operationCode] =
 			instruction: instruction
 			addressingMode: addressingMode
 			size: size
@@ -43,13 +63,19 @@
 		@negativeFlag = 0

 	step: ->
+		operation = @readOperation
+		address = @computeAddress operation.addressingMode
+		// TODO implement rest
+
+	readOperation: () ->
 		operationCode = @read @programCounter
-		instructionData = @instructionsTable[operationCode]
-		instruction = @instructionData.instruction
-		addressingMode = @instructionData.addressingMode
-		size = @instructionData.size
-		cycles = @instructionData.cycles
-		// TODO implement rest
+		@operationsTable[operationCode]
+
+	computeAddress: (addressingMode) ->
+		@addressingModesTable[addressingMode]()
+
+	executeInstruction: (instruction, address) ->
+		@instructionsTable[instruction](address)

 	push: (value) ->
 		@stackPointer = (@stackPointer - 1) & 0xFF


Clean up saveToFile

Clean up saveToFile

--- src/app.coffee
+++ src/app.coffee
@@ -8,6 +8,7 @@
 exec = require('child_process').exec
 os = require('os')
 async = require('async')
+fs = Promise.promisifyAll(require('fs'))

 config = require('./wifi.json')
 ssid = process.env.PORTAL_SSID or config.ssid
@@ -79,16 +80,15 @@
 		startServer(wifi)

 saveToFile = (ssid, passphrase) ->
-	if connectionsFromFile?
-		connectionsFromFile.push({ ssid, passphrase })
-	else
-		connectionsFromFile = [ { ssid, passphrase } ]
-	return new Promise (resolve, reject) ->
-		fs.writeFile connectionFile, JSON.stringify(connectionsFromFile), (err) ->
-			return reject(err) if err?
-			exec 'sync', (err) ->
-				reject(err) if err?
-				resolve()
+	connectionsFromFile ?= []
+	connectionsFromFile.push({ ssid, passphrase })
+	fs.openAsync(connectionFile, 'w')
+	.tap (fd) ->
+		fs.writeAsync(fd, JSON.stringify(connectionsFromFile))
+	.tap (fd) ->
+		fs.fsyncAsync(fd)
+	.then (fd) ->
+		fs.closeAsync(fd)

 console.log('Starting node connman app')
 manageConnection = (retryCallback) ->


Change the way WorldMap represents and transforms mapDef

--- src/javascript/game/map/world_map.coffee
+++ src/javascript/game/map/world_map.coffee
@@ -9,11 +9,12 @@
 RoomDefs = require './room_defs'
 Types = require './types'

-instanceRoom = (roomDef,r,c) ->
+instanceRoom = (roomDef,area,r,c) ->
   if roomDef?
     room = new Types.Room
       id: "room_r#{r}_c#{c}"
       roomDef: roomDef
+      area: area
       row:r
       col:c
       x: c * Config.roomWidthInPixels
@@ -23,13 +24,56 @@
   else
     null

+updateBounds = (area,r,c) ->
+  if !area.rowColBounds.leftCol?
+    area.rowColBounds.leftCol = c
+  else if c < area.rowColBounds.leftCol
+    area.rowColBounds.leftCol = c
+
+  if !area.rowColBounds.rightCol?
+    area.rowColBounds.rightCol = c
+  else if c > area.rowColBounds.rightCol
+    area.rowColBounds.rightCol = c
+
+  if !area.rowColBounds.topRow?
+    area.rowColBounds.topRow = r
+  else if r < area.rowColBounds.topRow
+    area.rowColBounds.topRow = r
+
+  if !area.rowColBounds.bottomRow?
+    area.rowColBounds.bottomRow = r
+  else if r > area.rowColBounds.bottomRow
+    area.rowColBounds.bottomRow = r
+
+  area.bounds.left   =  area.rowColBounds.leftCol      * Config.roomWidthInPixels
+  area.bounds.top    =  area.rowColBounds.topRow       * Config.roomHeightInPixels
+  area.bounds.right  = (area.rowColBounds.rightCol+1)  * Config.roomWidthInPixels
+  area.bounds.bottom = (area.rowColBounds.bottomRow+1) * Config.roomHeightInPixels
+
+getArea = (areas,name,r,c) ->
+  area = areas[name]
+  if !area?
+    area = new Types.Area
+      name: name
+      music: "brinstar"
+      rowColBounds: {}
+      bounds: {}
+    areas[name] = area
+  updateBounds(area,r,c)
+  return area
+
 # Convert a grid of room types into a grid of room data objects
-mapLayoutToRoomGrid = (mapLayout, roomDefs, mapConfig) ->
-  roomGrid = emptyGrid(mapLayout.rows, mapLayout.cols)
-  for row,r in mapLayout.data
-    for roomTypeId,c in row
-      roomDef = roomDefs.get(roomTypeId)
-      roomGrid[r][c] = instanceRoom(roomDef,r,c)
+mapDefToRoomGrid = (mapDef, roomDefs, mapConfig) ->
+  areas = {}
+  roomGrid = emptyGrid(mapDef.rows, mapDef.cols)
+  for row,r in mapDef.data
+    for pair,c in row
+      if pair?
+        [roomTypeId,areaName] = pair.split("-")
+        roomTypeId = parseInt(roomTypeId,16)
+        roomDef = roomDefs.get(roomTypeId)
+        area = getArea(areas, areaName, r, c)
+        roomGrid[r][c] = instanceRoom(roomDef,area,r,c)

   roomGrid

@@ -120,7 +164,7 @@
     music: "brinstar"

 class WorldMap
-  constructor: ({@roomGrid,@tileGrid,@areas}) ->
+  constructor: ({@roomGrid,@tileGrid}) ->
     [@rooms, @_roomsById] = indexRoomGrid(@roomGrid)

   # Return an array of Rooms that overlap the given px rectangle
@@ -150,34 +194,32 @@
     else
       null

-  @create: (layout) ->
-
-expandWorldMap = (layout) ->
-  roomGrid = mapLayoutToRoomGrid(layout, RoomDefs)
+expandWorldMap = (mapDef) ->
+  roomGrid = mapDefToRoomGrid(mapDef, RoomDefs)
   tileGrid = roomGridToTileGrid(roomGrid)
-  areas = _.map layout.areas, (areaDef) -> makeArea(areaDef)
-  setRoomAreas(roomGrid,areas)
+  # areas = _.map layout.areas, (areaDef) -> makeArea(areaDef)
+  # setRoomAreas(roomGrid,areas)
   new WorldMap
     roomGrid: roomGrid
     tileGrid: tileGrid
-    areas: areas
-
-mapDef =
-  rows: 10
-  cols: 10
+
+proto_mapDef =
+  rows: 5
+  cols: 15
   data: [
-    # mainEntry-------------------  |--|   roomA-----------------   roomB-----------------------
-    [0x08, 0x17, 0x09, 0x14, 0x13,  0x18,  0x12, 0x14, 0x19, 0x13,  0x12, 0x14, 0x14, 0x14, 0x13]
+    # mainEntry-------------------                       |--|       roomA-----------------   roomB-----------------------
+    ['0x13-E',null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]
+    ['0x08-A', '0x17-A', '0x09-A', '0x14-A', '0x13-A',  '0x18-B',  '0x12-C', '0x14-C', '0x19-C', '0x13-C',  '0x12-D', '0x14-D', '0x14-D', '0x14-D', '0x13-D']
   ]
-  areas: [
-    ["mainEntry", [0,0], [0,4]]
-    ["bridge", [0,5], [0,5]]
-    ["roomA", [0,6], [0,9]]
-    ["roomB", [0,10], [0,14]]
-  ]
+  # areas: [
+  #   ["mainEntry", [0,0], [0,4]]
+  #   ["bridge", [0,5], [0,5]]
+  #   ["roomA", [0,6], [0,9]]
+  #   ["roomB", [0,10], [0,14]]
+  # ]

 module.exports =
-  getDefaultWorldMap: FnUtils.memoizeThunk -> expandWorldMap(mapDef)
+  getDefaultWorldMap: FnUtils.memoizeThunk -> expandWorldMap(proto_mapDef)

 window.WorldMap = module.exports.getDefaultWorldMap


IDE: updateMachineRoot on handleIDEBecameReady and initial states

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -492,6 +492,18 @@
       @openFile { file, contents, switchIfOpen: yes }

+  updateMachineRoot: ->
+
+    { finderController } = @finderPane
+
+    @mountedMachine.fetchInfo (err, info) =>
+      debug 'machine.fetchInfo res', { err, info }
+      kd.warn '[IDE] Failed to fetch info', err  if err
+      rootPath = info?.home or '/'
+      debug 'updating machine root as', @mountedMachine.uid, rootPath
+      finderController.updateMachineRoot @mountedMachine.uid, rootPath
+
+
   mountMachine: (machineData) ->

     # interrupt if workspace was changed
@@ -500,8 +512,9 @@
     panel     = @workspace.getView()
     filesPane = panel.getPaneByName 'filesPane'

-    @workspace.ready ->
+    @workspace.ready =>
       filesPane.emit 'MachineMountRequested', machineData
+      @updateMachineRoot()

   unmountMachine: (machineData) ->
@@ -623,6 +636,7 @@
           @prepareCollaboration()
           @bindKlientEvents machineItem
           @runOnboarding()
+
         else
           unless @machineStateModal

@@ -1232,19 +1246,12 @@
   handleIDEBecameReady: (machine, initial = no) ->

     { computeController } = kd.singletons
-    { finderController }  = @finderPane

     debug 'handleIDEBecameReady', { machine, initial }

     # when MachineStateModal calls this func, we need to rebind Klient.
     @bindKlientEvents machine
-
-    machine.fetchInfo (err, info) =>
-      debug 'machine.fetchInfo res', { err, info }
-      kd.warn '[IDE] Failed to fetch info', err  if err
-      rootPath = info?.home or '/'
-      debug 'updating machine root as', @mountedMachine.uid, rootPath
-      finderController.updateMachineRoot @mountedMachine.uid, rootPath
+    @updateMachineRoot()

     machine.getBaseKite()?.fetchTerminalSessions?()


fix analytics

fix analytics

--- client/lib/analytics.coffee
+++ client/lib/analytics.coffee
@@ -1,34 +1,44 @@
 analytics = analytics or []
 analytics.load = (apiKey) ->
-	script = document.createElement("script")
-	script.type = "text/javascript"
-	script.async = true
-	script.src = ((if "https:" is document.location.protocol then "https://" else "http://")) + "d2dq2ahtl5zl1z.cloudfront.net" + "/analytics.js/v1/" + apiKey + "/analytics.min.js"
-	first = document.getElementsByTagName("script")[0]
-	first.parentNode.insertBefore script, first
-	factory = (type) ->
-		->
-			analytics.push [type].concat(Array::slice.call(arguments_, 0))
+    script = document.createElement("script")
+    script.type = "text/javascript"
+    script.async = true
+    script.src = ((if "https:" is document.location.protocol then "https://" else "http://")) + "d2dq2ahtl5zl1z.cloudfront.net" + "/analytics.js/v1/" + apiKey + "/analytics.min.js"
+    first = document.getElementsByTagName("script")[0]
+    first.parentNode.insertBefore script, first
+    factory = (type) ->
+        ->
+            analytics.push [type].concat(Array::slice.call(arguments, 0))

-	methods = ["identify", "track"]
-	i = 0
+    methods = ["identify", "track"]
+    i = 0

-	while i < methods.length
-		analytics[methods[i]] = factory(methods[i])
-		i++
-
+    while i < methods.length
+        analytics[methods[i]] = factory(methods[i])
+        i++
+
 analyticsRequest = ->
-	segementIoId = getSetting('segmentIoId')
-	if segementIoId
-		analytics.load(segementIoId)
+    segementIoId = getSetting('segmentIoId')
+    if segementIoId
+        analytics.load(segementIoId)

-		Meteor.autorun ->
-			user = Meteor.user()
-			if user and user.profile and user.answerQuestionCount and user.askQuestionCount
-				analytics.identify user._id,
-					name: user.profile.name
-					email: user.profile.facebook.email
-					createdAt: user.createdAt
-					karma: user.karma
-					answerQuestionCount: user.answerQuestionCount
-					askQuestionCount: user.askQuestionCount+        Meteor.autorun ->
+            user = Meteor.user()
+            if user and user.profile and user.profile.facebook and user.answerQuestionCount and user.askQuestionCount
+                analytics.identify user._id,
+                    name: user.profile.name
+                    email: user.profile.facebook.email
+                    username: user.profile.facebook.username
+                    gender: user.profile.facebook.gender
+                    birthday: user.profile.facebook.birthday
+                    locale: user.profile.facebook.locale
+                    timezone: user.profile.facebook.timezone
+                    bio: user.profile.facebook.bio
+                    quotes: user.profile.facebook.quotes
+                    education: user.profile.facebook.education
+                    work: user.profile.facebook.work
+                    inspirationalPeople: user.profile.facebook.inspirational_people
+                    createdAt: user.createdAt
+                    karma: user.karma
+                    answerQuestionCount: user.answerQuestionCount
+                    askQuestionCount: user.askQuestionCount


version check over HTTPS

version check over HTTPS

--- client/version_check.coffee
+++ client/version_check.coffee
@@ -10,7 +10,7 @@

 check_version = ->
   $.ajax(
-    url: "http://strawlab.org/neuron-catalog/latest-release.json"
+    url: "https://s3-eu-west-1.amazonaws.com/strawlab.org/neuron-catalog/latest-release.json"
     dataType: 'json'
     success: on_version_received
   )


do not show clear search icon (x) because it's confusing

--- src/scripts/modules/media/nav/nav.coffee
+++ src/scripts/modules/media/nav/nav.coffee
@@ -162,6 +162,11 @@
       removeClass('fa-search').
       addClass('fa-spinner fa-spin load-search')

+    enableSearch: ->
+      @$el.find('.searchbar > .fa').
+      removeClass('fa-spinner fa-spin load-search').
+      addClass('fa-search')
+
     handleSearch: ->
       @searchTerm = @$el.find('.searchbar input').val()
       if @searchTerm == ''
@@ -178,7 +183,7 @@
         if not @tocIsOpen
           @toggleContents()
         @model.set('searchResults', data.results)
-        @enableClearSearch()
+        @enableSearch()
       ).fail((err) ->
         console.error("Search failed:", err)
       )


CredentialsUI: cleanup data calls

CredentialsUI: cleanup data calls

--- client/Account/views/credentials.coffee
+++ client/Account/views/credentials.coffee
@@ -225,7 +225,8 @@
     options.cssClass = KD.utils.curry "credential-item", options.cssClass
     super options, data

-    delegate = @getDelegate()
+    delegate  = @getDelegate()
+    { owner } = @getData()

     @deleteButton = new KDButtonView
       title    : "Delete"
@@ -235,18 +236,19 @@
     @shareButton = new KDButtonView
       title    : "Share"
       cssClass : "solid small green"
-      disabled : !@getData().owner
+      disabled : !owner
       callback : => delegate.shareItem this

     @showCredentialButton = new KDButtonView
       title    : "Show Content"
       cssClass : "solid small green"
-      disabled : !@getData().owner
+      disabled : !owner
       callback : => delegate.showItemContent this

     @participantsButton = new KDButtonView
       title    : "Show Participants"
       cssClass : "solid small green"
+      disabled : !owner
       callback : => delegate.showItemParticipants this

   pistachio:->


Pricing: custom quote button taken out of dev plan

--- client/Pricing/views/productform.coffee
+++ client/Pricing/views/productform.coffee
@@ -24,6 +24,10 @@
       name: 'Team'
       view: @teamPlan

+    @teamPlan.once 'viewAppended', =>
+      KD.utils.defer =>
+        @teamPlan.addSubView new PricingCustomQuoteView
+
   showSection: (name) ->
     @tabView.showPaneByName name

@@ -37,6 +41,5 @@
   viewAppended: ->
     @addSubView new PricingIntroductionView
     @addSubView @tabView
-    @addSubView new PricingCustomQuoteView
     @addSubView new PricingFeaturesView
     @addSubView new FooterView


Migrate tags module

Migrate tags module

--- modules/tags.coffee
+++ modules/tags.coffee
@@ -1,12 +1,12 @@
 Chance = require 'chance'

 class TagModule extends BotModule
-  init: =>
-    { @permissions } = @engine
+  init: ->
+    @defaultDisabled = true
+    { permissions } = Core

-    @registerCommand '+', { argSeparator: ' ' }, (msg, args, d)=>
+    @registerCommand '+', { argSeparator: ' ' }, ({ msg, args, l })=>
       return if msg.author.bot or args.length < 2
-      return unless d.data.allowTags
       # Get or create a new tag
       tag = (await Core.data.get("Tag:#{args[0].toLowerCase()}")) or []
       # Add the response
@@ -16,21 +16,20 @@
       }
       # Save
       await Core.data.set("Tag:#{args[0].toLowerCase()}", tag)
-      msg.reply 'Tag saved!'
+      msg.reply l.tags.added

-    @registerCommand '-', { argSeparator: ' ' }, (msg, args, d)=>
+    @registerCommand '-', { argSeparator: ' ' }, ({ msg, args, l })=>
       return if msg.author.bot or args.length < 1
-      return unless d.data.allowTags
       # Try to get existing tag
       tag = await Core.data.get("Tag:#{args[0].toLowerCase()}")
-      return msg.reply 'Deleted 0 tag(s)!' unless tag?
+      return msg.reply l.gen(l.tags.deleted, 0) unless tag?
       # Filter out the desired tags
       filtered = tag.filter (reply)=>
         if args.length > 1
           # Owner is allowed to delete all responses of a tag
-          return false if args[1] is 'all' and @permissions.isOwner msg.author
+          return false if args[1] is 'all' and permissions.isOwner msg.author
           # Also allowed to delete responses made by others
-          if reply.reply is args.slice(1).join(' ') and @permissions.isOwner msg.author
+          if reply.reply is args.slice(1).join(' ') and permissions.isOwner msg.author
             return false
           # Other people can only delete their own
           return false if reply.by is msg.author.id and reply.reply is args.slice(1).join(' ')
@@ -40,29 +39,28 @@
         true
       # Save the changes
       await Core.data.set("Tag:#{args[0].toLowerCase()}", filtered)
-      msg.reply "Deleted #{tag.length - filtered.length} tag(s)!"
+      msg.reply l.gen(l.tags.deleted, tag.length - filtered.length)

-    @registerCommand '!', { argSeparator: ' ' }, (msg, args, d)=>
+    @registerCommand '!', { argSeparator: ' ' }, ({ msg, args })=>
       return if msg.author.bot or args.length < 1
-      return unless d.data.allowTags
       # Get the tag
       tag = await Core.data.get("Tag:#{args[0].toLowerCase()}")
       return unless tag?
       # Pick a random reply and send it
       chance = new Chance()
       res = chance.pickone tag
-      msg.channel.sendMessage res.reply
+      msg.channel.send res.reply

-    @registerCommand 'taginfo', { ownerOnly: true, argSeparator: ' ' }, (msg, args, data, bot)=>
+    @registerCommand 'taginfo', { ownerOnly: true, argSeparator: ' ' }, ({ msg, args, bot })=>
       # Get the tag
       tag = await Core.data.get("Tag:#{args[0].toLowerCase()}")
       return unless tag?
       # Generate a list of responses
       r = ''
       for res in tag
-        u = bot.Users.get(res.by) or { username: 'Unknown', discriminator: res.by }
+        u = bot.users.find('id', res.by) or { username: 'Unknown', discriminator: res.by }
         r += "\n(#{u.username}##{u.discriminator}): #{res.reply.substr(0,32)}..."
       # Send it
-      msg.channel.sendMessage r
+      msg.channel.send r

 module.exports = TagModule


Badge: add protected trait

Badge: add protected trait

--- workers/social/lib/social/models/badge.coffee
+++ workers/social/lib/social/models/badge.coffee
@@ -5,6 +5,7 @@
   KodingError       = require '../error'

   @trait __dirname, '../traits/filterable'
+  @trait __dirname, '../traits/protected'

   @share()
   @set


only set loading to false when view is found

--- client/lanes/screens/Definitions.coffee
+++ client/lanes/screens/Definitions.coffee
@@ -90,11 +90,15 @@
         return new _.Promise( (resolve, reject) ->
             me.loading = true
             attempt = 0
+            onViewLoaded = (view) ->
+                me.loading = false
+                resolve(view)
             done = ->
-                me.loading = false
                 viewModel = me.getScreen()
                 if viewModel
-                    resolve(me)
+                    _.delay ->
+                        onViewLoaded(viewModel)
+                    100
                 else if attempt < 3
                     attempt += 1
                     _.delay(done, 500 * attempt)
@@ -172,7 +176,7 @@
         screen.set(active:true)

     isLoading: ->
-        !!Lanes.Screens.Definitions.all.findWhere({loading: true})
+        !!@findWhere(loading: true)

 class MenuGroup extends Lanes.Models.BasicModel


Test: Myteam remove unnecessary pauses

Test: Myteam remove unnecessary pauses

--- client/test/lib/dashboard/myteam.coffee
+++ client/test/lib/dashboard/myteam.coffee
@@ -71,19 +71,17 @@
     targetUser1 = utils.getUser no, 1
     teamsHelpers.logoutTeam browser, (res) ->
       teamsHelpers.loginToTeam browser, targetUser1 , no, ->
-        browser.pause 3000
-        .pause 3000
+        browser
         .waitForElementVisible welcomeView, 20000
         .url myTeamLink
         .waitForElementVisible sectionSelector, 20000
-        .waitForElementNotPresent removeLogoButton, 2000
-        .waitForElementNotPresent uploadLogoButton, 2000
-        .waitForElementNotPresent saveChangesButton + ':nth-of-type(1)', 2000
+        .waitForElementNotPresent removeLogoButton, 20000
+        .waitForElementNotPresent uploadLogoButton, 20000
+        .waitForElementNotPresent saveChangesButton + ':nth-of-type(1)', 20000
         .assert.attributeEquals teamNameSelector, 'disabled', 'true'
         .waitForElementVisible leaveTeamButton, 20000
         .click leaveTeamButton
         .waitForElementVisible '.kdmodal.kddraggable', 5000
-        .pause 2000

         .click forgotPasswordButton
         .waitForElementVisible  '.kdnotification', 5000
@@ -92,7 +90,6 @@

         .click leaveTeamButton
         .waitForElementVisible '.kdmodal.kddraggable', 5000
-        .pause 2000
         .clearValue passwordSelector
         .setValue passwordSelector, '1234'
         .click confirmButton
@@ -102,12 +99,11 @@

         .click leaveTeamButton
         .waitForElementVisible '.kdmodal.kddraggable', 5000
-        .pause 2000
         .clearValue passwordSelector
         .setValue passwordSelector, targetUser1.password
         .click confirmButton
         .assert.urlContains helpers.getUrl(yes)
-        .end
+        .end()

   inviteAndJoinUsersToTeam: (browser) ->


feat(a11y): label back buttons with "Back to XYZ"

--- modules/scripts/directives/button.coffee
+++ modules/scripts/directives/button.coffee
@@ -3,5 +3,13 @@
 angular.module('bp.directives').directive 'bpButton', ->
   restrict: 'E'
   link: (scope, element, attrs) ->
-    element.attr
+    attributes =
       role: 'button'
+
+    if element.hasClass('bp-button-back') and not attrs['aria-label']
+      attributes['aria-label'] = if label = element.text()
+        "Back to #{label}"
+      else
+        "Back"
+
+    element.attr attributes


Disable mouse wheel events in Leaflet controls

--- src/views.coffee
+++ src/views.coffee
@@ -30,6 +30,14 @@
             map = @map
             _.each @map_controls,
                 (control, key) -> control.addTo map
+
+            # Disable wheel events to map controls so that the map won't zoom
+            # if we try to scroll in a control.
+            $('.leaflet-control-container').on 'mousewheel', (ev) ->
+                ev.stopPropagation()
+                window.target = ev.target
+                return
+
             return this
         remember_markers: (service_id, markers) ->
             @current_markers[service_id] = markers


spec for Cookies resource (angular)

spec for Cookies resource (angular)


Adding snippets for class and liquid starter templates

--- atom.symlink/snippets.cson
+++ atom.symlink/snippets.cson
@@ -26,3 +26,9 @@
   'CX Grid Col':
     'prefix': 'cxgc'
     'body': '
\n  $1\n
'
+  'class':
+    'prefix': 'c'
+    'body': 'class="$1"'
+  'Liquid Starter Template':
+    'prefix': 'liquid'
+    'body':  '{% layout \'bare\' %}\n{% block page_title %}$1{% endblock %}\n{% block custom_head %}\n  \n{% endblock %}\n{% block content %}\n
\n
$1
\n
\n{% endblock %}\n{% block custom_scripts %}\n{% endblock %}\n'


exec not execSync

exec not execSync


Cleanup autocomplete. Make use of ngModel controller.

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -37,10 +37,10 @@
     replace:     true
     require:     "?ngModel"

-    link: ($scope, element, attrs) ->
-      labelKey      = attrs.macAutocompleteLabel       or "name"
-      queryKey      = attrs.macAutocompleteQuery       or "q"
-      delay         = +attrs.macAutocompleteDelay      or 800
+    link: ($scope, element, attrs, ctrl) ->
+      labelKey = attrs.macAutocompleteLabel  or "name"
+      queryKey = attrs.macAutocompleteQuery  or "q"
+      delay    = +attrs.macAutocompleteDelay or 800

       autocompleteUrl     = $parse attrs.macAutocompleteUrl
       onSelect            = $parse attrs.macAutocompleteOnSelect
@@ -49,11 +49,12 @@
       source              = $parse attrs.macAutocompleteSource
       currentAutocomplete = []

-      # TODO
-      if attrs.ngModel?
+      # HACK: setTimeout is added to make sure value wont' change afterwards
+      if ctrl?
         $scope.$watch attrs.ngModel, (value) ->
           setTimeout ->
-            element.val value
+            ctrl.$setViewValue value
+            ctrl.$render()
           , 0

       #
@@ -62,8 +63,7 @@
       # @description
       # Resetting autocomplete
       #
-      reset = ->
-        currentAutocomplete = []
+      reset = -> currentAutocomplete = []

       #
       # @function
@@ -102,15 +102,12 @@

           $http(options)
             .success (data, status, headers, config) ->
-                if onSuccess?
-                  fetchedList = onSuccess {data, status, headers}
-
+                fetchedList  = onSuccess?({data, status, headers})
                 fetchedList ?= data.data

                 resp updateList fetchedList
             .error (data, status, headers, config) ->
-              if onError?
-                onError {data, status, headers}
+              onError? {data, status, headers}
         else
           list = updateList(source($scope) or [])
           resp $filter("filter") list, req.term
@@ -137,6 +134,4 @@
       # Event to reset autocomplete
       #
       $scope.$on "resetAutocomplete", -> reset()
-
-      reset()
 ]


feature(macAutocomplete): Uses $animate to append menu.
Menu scope is fully isolated to prevent affecting $rootScope.

BREAKING CHANGE:
Changed autocomplete event from resetAutocomplete to reset-mac-autocomplete

Related to #124

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -29,6 +29,7 @@
 ###

 angular.module("Mac").directive "macAutocomplete", [
+  "$animate"
   "$http"
   "$filter"
   "$compile"
@@ -36,7 +37,7 @@
   "$parse"
   "$rootScope"
   "keys"
-  ($http, $filter, $compile, $timeout, $parse, $rootScope, keys) ->
+  ($animate, $http, $filter, $compile, $timeout, $parse, $rootScope, keys) ->
     restrict:    "E"
     templateUrl: "template/autocomplete.html"
     replace:     true
@@ -59,7 +60,7 @@
       timeoutId           = null
       onSelectBool        = false

-      $menuScope       = $rootScope.$new()
+      $menuScope       = $rootScope.$new(true)
       $menuScope.items = []
       $menuScope.index = 0

@@ -76,8 +77,8 @@
         # If value is more than an empty string,
         # autocomplete is enabled and not 'onSelect' cycle
         if value and not disabled($scope) and not onSelectBool
+          $timeout.cancel timeoutId if timeoutId?
           if delay > 0
-            $timeout.cancel timeoutId if timeoutId?
             timeoutId = $timeout ->
               queryData value
             , delay
@@ -217,9 +218,9 @@

       #
       # @event
-      # @name resetAutocomplete
+      # @name reset-mac-autocomplete
       # @description
       # Event to reset autocomplete
       #
-      $scope.$on "resetAutocomplete", -> reset()
+      $scope.$on "reset-mac-autocomplete", -> reset()
 ]


test(lxd): remove debug instruction

test(lxd): remove debug instruction

--- packages/lxd/test/config/device/delete.coffee
+++ packages/lxd/test/config/device/delete.coffee
@@ -37,7 +37,6 @@
         image: 'ubuntu:'
         container: 'c1'
       @lxd.config.device
-        metadata: debug: true
         container: 'c1'
         device: 'test'
         type: 'unix-char'


SocialWorker: Tests: fix tests

SocialWorker: Tests: fix tests

--- workers/social/lib/social/models/account.test.coffee
+++ workers/social/lib/social/models/account.test.coffee
@@ -417,7 +417,10 @@

       withConvertedUser ({ account }) ->

-        account.migrateOldAppStorageIfExists {}, (err, storage) ->
+        version             = generateRandomString()
+        appId               = generateRandomString()
+
+        account.migrateOldAppStorageIfExists { appId, version }, (err, storage) ->
           expect(err).to.not.exist
           expect(storage).to.not.exist
           done()


payment: whooops, pass the callback thru

--- workers/social/lib/social/models/payment/plan.coffee
+++ workers/social/lib/social/models/payment/plan.coffee
@@ -144,10 +144,11 @@
   remove$: permit 'manage products',
     success: (client, callback) -> @remove callback

-  modify: (formData, callback) -> @update $set: formData, callback
+  modify: (formData, callback) ->
+    @update $set: formData, callback

   modify$: permit 'manage products',
-    success: (client, formData, callback) -> @modify formData
+    success: (client, formData, callback) -> @modify formData, callback

   fetchToken: secure (client, data, callback) ->
     JPaymentToken.createToken client, planCode: @planCode, callback


Fix for Firefox

Fix for Firefox

--- app/assets/javascript/active_admin/gallery/sortable.js.coffee
+++ app/assets/javascript/active_admin/gallery/sortable.js.coffee
@@ -24,6 +24,6 @@
         update: (event, ui) ->
           $(@).find("input[id$=_position]").each (index) -> $(@).val(index + 1)

-  $(document).on "click", "a", -> setTimeout(setup, 50)
+  $("a").bind "click", -> setTimeout(setup, 50)

   setup()