Highlight in more responsive way

Highlight in more responsive way

--- lib/Watcher.coffee
+++ lib/Watcher.coffee
@@ -61,8 +61,7 @@
     # Start listening
     @editorView.on 'cursor:moved', @onCursorMoved
     @editor.on 'destroyed', @onDestroyed
-    # @editor.on 'contents-modified', @onContentsModified
-    @editor.buffer.on 'changed', @onContentsModified
+    @editor.buffer.on 'changed', @onBufferChanged

     # Execute
     @parse()
@@ -71,8 +70,9 @@
     # Stop listening
     @editorView.off 'cursor:moved', @onCursorMoved
     @editor.off 'destroyed', @onDestroyed
-    # @editor.off 'contents-modified', @onContentsModified
-    @editor.buffer.off 'changed', @onContentsModified
+    @editor.buffer.off 'changed', @onBufferChanged
+    clearTimeout @bufferChangedTimeoutId
+    clearTimeout @cursorMovedTimeoutId

     # Destruct instances
     @ripper?.destruct()
@@ -82,6 +82,8 @@
     @statusView?.destruct()

     # Remove references
+    delete @bufferChangedTimeoutId
+    delete @cursorMovedTimeoutId
     delete @module
     delete @ripper
     delete @referenceView
@@ -99,7 +101,7 @@
   5. Start listening cursor move event.
   ###

-  parse: ->
+  parse: =>
     @editorView.off 'cursor:moved', @onCursorMoved
     @hideError()
     @referenceView.update()
@@ -201,14 +203,13 @@
   User events
   ###

-  onContentsModified: =>
-    @parse()
+  onBufferChanged: =>
+    clearTimeout @bufferChangedTimeoutId
+    @bufferChangedTimeoutId = setTimeout @parse, 0

   onCursorMoved: =>
-    # clearTimeout @timeoutId
-    # @timeoutId = setTimeout @updateReferences, 0
-    # nextTick => @updateReferences()
-    @updateReferences()
+    clearTimeout @cursorMovedTimeoutId
+    @cursorMovedTimeoutId = setTimeout @updateReferences, 0

   ###


add docs for main object

add docs for main object

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -1,4 +1,7 @@

+###
+  RoutePart is a parser for string route representation
+###
 class RoutePart
   constructor: (route) ->
     @end   = yes
@@ -60,15 +63,45 @@
     @args = args
     true

+###
+  Main Object.

+  @example:
+    MyController =
+      my_action: (id) ->
+        #...
+
+      when_click: (event) ->
+        #...
+
+    routes =
+      '#/action/:id'  : [MyController, "my_action"]
+      'click #button" : [MyController, "my_action"]
+      404: (current_url) ->
+        alert("#{current_url} not found!")
+
+    adapter = new JQueryAdapter();
+
+    SiriusApplication.run({adapter: adapter, route: routes})
+###
 SiriusApplication =
+  ###
+    Bind for routes appropriate events and callbacks
+  ###
   RouteSystem:
+    #
+    #@param [Object] is a routes object where keys is a route and value is a array where
+    # first element is a controller name, and second element is a action
+    # or it's a function
+    # @todo: custom events
     create: (routes) ->
       current = prev = window.location.hash

       is_f = (f) ->
         Object.prototype.toString.call(f) == '[object Function]'

+
+      #TODO: add filters: before\after\wrap
       #[Controller, "method"] => Controller[method]
       a2f = (a) ->
         throw "#{a} must be array or function" if Object.prototype.toString.call(a) isnt '[object Array]'
@@ -80,8 +113,15 @@
         throw "Action must be a Function" if Object.prototype.toString.call(f) isnt '[object Function]'
         f

+      is_custom_event = (str) ->
+        str.indexOf(":") != -1
+
+      #TODO: custom event support
       for url, action of routes when url.indexOf("#") != 0 && url.toString() != "404"
         do (url, action) =>
+          if is_custom_event(url)
+            throw "Not supported event type for #{url}"
+
           z = url.match(/^([a-zA-Z]+)\s+(.*)/)
           event_name = z[1]
           selector = z[2]
@@ -117,9 +157,13 @@
         if !result
           (if routes['404'] then a2f(routes['404']) else empty)(current)

+  #@note not implemented
   log: false
+  #adapter for application @see adapter documentation
   adapter: null
+  #boolean
   running: false
+  #route object
   route: {}
   run: (options = {}) ->
     @running = true


Reduced spamminess of server.

Reduced spamminess of server.

--- lib/coo/coo.coffee
+++ lib/coo/coo.coffee
@@ -39,10 +39,8 @@
     require('http').createServer( (request, response) ->
       request.addListener 'end', ->
         file.serve request, response, (err, res) ->
-          if err?
+          if err? and not request.url.match /favicon\.ico/
             console.error "Error serving #{request.url} - #{err.message}"
             response.writeHead err.status, err.headers
-            response.end()
-          else
-            console.log "#{request.url} - #{res.message}").listen 8080
+            response.end()).listen 8080
     console.log "Server is listening on http://127.0.0.1:8080"


sidebarwidget: minor update

sidebarwidget: minor update

--- client/app/lib/components/sidebarstacksection/stackupdatedwidget.coffee
+++ client/app/lib/components/sidebarstacksection/stackupdatedwidget.coffee
@@ -18,7 +18,7 @@

   componentWillReceiveProps: (nextProps) ->

-    @setState { isShown : no }  if nextProps.show
+    @setState { isShown : not nextProps.show }

   handleOnClick : ->


Futureproof theme against impending jQuery removal

--- lib/headers.coffee
+++ lib/headers.coffee
@@ -5,7 +5,7 @@
   projectRoots = treeView.roots

   updateTreeViewHeaderPosition = ->
-    yScrollPosition = treeView.scroller[0].scrollTop
+    yScrollPosition = (treeView.scroller[0] ? treeView.scroller).scrollTop

     for project in projectRoots
       projectHeaderHeight = project.header.offsetHeight
@@ -32,7 +32,11 @@
     # TODO something other than setTimeout? it's a hack to trigger the update
     # after the CSS changes have occurred. a gamble, probably inaccurate
     setTimeout -> updateTreeViewHeaderPosition()
-  treeView.scroller.on 'scroll', updateTreeViewHeaderPosition
+  if typeof treeView.scroller.on is 'function'
+    treeView.scroller.on 'scroll', updateTreeViewHeaderPosition
+  else
+    treeView.scroller.addEventListener 'scroll', ->
+      updateTreeViewHeaderPosition()

   setTimeout -> # TODO something other than setTimeout?
     updateTreeViewHeaderPosition()


Some clean up.

Some clean up.

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -36,12 +36,12 @@
     return new Promise (resolve, reject) ->
       process = child_process.exec(command, options)
       options.stream = 'stdout' if not options.stream
-      data = []
-      if options.stream == 'stderr'
-        process.stderr.on 'data', (d) -> data.push(d.toString())
-      else
-        process.stdout.on 'data', (d) -> data.push(d.toString())
+      data = ['']
+      process.stdout.on 'data', (d) -> data.push(d.toString()) if options.stream == 'stdout'
+      process.stderr.on 'data', (d) -> data.push(d.toString()) if options.stream == 'stderr'
       process.stdin.write(options.stdin.toString()) if options.stdin
+      process.on 'error', (err) ->
+        reject(err)
       process.on 'close', ->
         resolve(data)

@@ -77,8 +77,8 @@
       toReturn = []
       regex = XRegExp(regex)
       for line in data
-        if regex.test line
-          match = XRegExp.exec(line, regex)
+        match = XRegExp.exec(line, regex)
+        if match
           options.baseReduction = 1 if not options.baseReduction
           lineStart = 0
           lineStart = match.line - options.baseReduction if match.line


Added a class attribute to instances of classes exported by indexed.coffee

--- lib/indexed.coffee
+++ lib/indexed.coffee
@@ -236,7 +236,7 @@
   # Generic update method with support for multiple arguments
   update: (args, step) ->
     newroot = util.reduce(args, step, @root)
-    if newroot != @root then new @collection(newroot) else this
+    if newroot != @root then new @class(newroot) else this

   # Returns the elements in this set as an array.
   toArray: -> @entries?.toArray() or []
@@ -268,8 +268,8 @@

 # The IntSet class.
 class IntSet extends Collection
+  this::class = this
   className: "IntSet"
-  collection: IntSet

   # Returns the elements as a stream
   elements: -> @entries
@@ -319,8 +319,8 @@

 # The IntMap class is essentially a huge sparse array.
 class IntMap extends Collection
+  this::class = this
   className: "IntMap"
-  collection: IntMap

   # Returns the (key,value)-pairs as a stream
   items: -> @entries
@@ -444,8 +444,8 @@
 # The HashSet class provides the public API and serves as a wrapper
 # for the various node classes that hold the actual information.
 class HashSet extends Collection
+  this::class = this
   className: "HashSet"
-  collection: HashSet

   # Returns the elements as a stream
   elements: -> @entries
@@ -507,8 +507,8 @@
 # The HashMap class provides the public API and serves as a wrapper
 # for the various node classes that hold the actual information.
 class HashMap extends Collection
+  this::class = this
   className: "HashMap"
-  collection: HashMap

   # Returns the (key,value)-pairs as a stream
   items: -> @entries


Enable search up front

Enable search up front

--- app/webroot/js/search.coffee
+++ app/webroot/js/search.coffee
@@ -1,4 +1,4 @@
-# Copyright © 2012 Martin Ueding <dev@martin-ueding.de>
+# Copyright © 2012-2013 Martin Ueding <dev@martin-ueding.de>
 # Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php).

 # Time for the scroll up and down animation. In ms.
@@ -49,7 +49,7 @@

 # Inserts the search field into the DOM and focuses it.
 showField = ->
-	$('#navigation').after '
<input id="search_input" type="text" />
'
+	$('#navigation').after '
<form action="bookmarks/search" method="post"><input id="search_input" name="data[query]" type="text" /></form>
'
 	$('#search_input').focus()

 # Attaches the listeners to the search input field.


Ignore build/Release/obj directory

Ignore build/Release/obj directory

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -54,6 +54,7 @@
       path.join('xmldom', 'test')
       path.join('jasmine-reporters', 'ext')
       path.join('build', 'Release', 'obj.target')
+      path.join('build', 'Release', 'obj')
       path.join('build', 'Release', '.deps')
       path.join('vendor', 'apm')
       path.join('resources', 'mac')


Hacks around header z-indexes with some existing class

--- components/search_bar/client/view.coffee
+++ components/search_bar/client/view.coffee
@@ -148,14 +148,19 @@
     mediator.trigger 'search:loaded'

   searchUnloaded: ->
-    @$el.removeClass('is-loading')
-    @$el.removeClass('is-active')
-    @$el.removeClass('has-results')
-    @$results.html ""
+    @$el
+      .removeClass('is-loading')
+      .removeClass('is-active')
+      .removeClass('has-results')
+        .parent().removeClass('is-active')
+
+    @$results.html ''

   blurSearch: (e) ->
     _.delay =>
       @$el.removeClass('is-active')
+        .parent().removeClass('is-active')
+
       $('.path').removeClass('is-hidden')
     , 200

@@ -163,6 +168,8 @@
     @searchUnloaded()
     @$input.val ""

-  focusSearch: (e)->
+  focusSearch: (e) ->
     @$el.addClass('is-active')
+      .parent().addClass('is-active')
+
     $('.path.stuck').addClass('is-hidden')


Add support for ng-change callbacks in editor directive

--- src/directive/editor.coffee
+++ src/directive/editor.coffee
@@ -15,7 +15,7 @@
     {
         restrict: 'E'
         templateUrl: '/directive/editor.html'
-        scope: ngModel: '=ngModel'
+        scope: ngModel: '=ngModel', ngChange: '=ngChange'
         link: (scope, element, attrs) ->
             options = defaults


Remove commented code from js

Remove commented code from js

--- app/assets/javascripts/neighborly/projects/contributions/index.js.coffee
+++ app/assets/javascripts/neighborly/projects/contributions/index.js.coffee
@@ -25,8 +25,6 @@
     masonry: ->
       this.$('.list').masonry ->
         itemSelector: '.contribution-wrapper'
-        #columnWidth : 230
-

     , Neighborly.InfiniteScroll)


:lipstick: Remove log line.

:lipstick: Remove log line.

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -480,7 +480,6 @@
     @selectToMousePositionUntilMouseUp(event)

   onClickGutter: (event) ->
-    console.log 'gutter click', event
     {editor} = @props
     {target} = event
     lineNumber = target.parentNode


remove old two way binding spec

remove old two way binding spec

--- spec/integration/two_way_bindings.spec.coffee
+++ spec/integration/two_way_bindings.spec.coffee
@@ -1,55 +0,0 @@
-{Serenade} = require '../../src/serenade'
-
-describe 'Two-way bindings', ->
-  beforeEach ->
-    @setupDom()
-
-  it 'updates plain model when event triggers', ->
-    model = {}
-    @render 'input[type="text" binding:keyup=name]', model, {}
-    input = @body.find('input').get(0)
-    input.value = "Test"
-    @fireEvent input, "keyup"
-    expect(model.name).toEqual("Test")
-
-  it 'updates serenade model when event triggers', ->
-    class MyModel extends Serenade.Model
-      @property 'name'
-    model = new MyModel()
-    @render 'input[type="text" binding:keyup=name]', model, {}
-    input = @body.find('input').get(0)
-    input.value = "Test"
-    @fireEvent input, "keyup"
-    expect(model.name).toEqual("Test")
-
-  it 'sets value of input to models value', ->
-    model = {name: "My name"}
-    @render 'input[type="text" binding:keyup=name]', model, {}
-    input = @body.find('input').get(0)
-    expect(input.value).toEqual("My name")
-
-  it 'sets value of textarea to models value', ->
-    model = {name: "My name"}
-    @render 'textarea[binding:keyup=name]', model, {}
-    input = @body.find('textarea').get(0)
-    expect(input.value).toEqual("My name")
-
-  it 'updates the value of input when model changes', ->
-    class MyModel extends Serenade.Model
-      @property 'name'
-    model = new MyModel({name: "My name"})
-    @render 'input[type="text" binding:keyup=name]', model, {}
-    model.set("name", "Changed name")
-    input = @body.find('input').get(0)
-    expect(input.value).toEqual("Changed name")
-
-  it 'rejects none-input elements', ->
-    expect(=> @render 'div[binding:keyup=name]', {}, {}).toThrow()
-
-  # Note: Jasmine seems to set input.value to "" when we set it to undefined.
-  # Actual browsers will set it to "undefined".
-  it 'sets value to empty string when model property is undefined', ->
-    model = {name: undefined}
-    @render 'input[type="text" binding:change=name]', model
-    input = @body.find("input").get(0)
-    expect(input.value).toEqual("")


all non served events are boubled up if @data.intercepted is true
or if @data.intercepted is an array that contains action as a string

boubling is always suppressed is suppressed for livecycle-events

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -707,7 +707,7 @@
                 do(action)=>
                     if typeof (handler = @[action]) is 'function'

-                        @on action, (event)=>
+                        @once action, (event)=>
                             handler.apply @, [event]

             if config.events
@@ -746,7 +746,15 @@
             for action in actions
                 event.type = action

-                @_emitter.emit action, event, args
+                stop = @_emitter.emit action, event, args
+
+                ## all non served events are boubled up if @data.intercepted is true
+                ## or if @data.intercepted is an array that contains action as a string
+
+                ## boubling is always suppressed is suppressed for livecycle-events
+                if !stop && (@data.intercepted is true || @data.intercepted?.indexOf action) && ACTIONS.indexOf(action) is -1
+
+                    @parent._emit action, event, args

             return @


Make true/false opinion view a radio button with an either toggle

--- @client/opinion_views.coffee
+++ @client/opinion_views.coffee
@@ -930,6 +930,10 @@

     passing_vals = (val for val,enabled of opinion_views_ui.visible_attribute_values[attr_key] when enabled)

+    if 'either' in passing_vals
+      passing_vals.push 'true'
+      passing_vals.push 'false'
+
     passes = false
     for passing_val in passing_vals

@@ -1067,13 +1071,23 @@
               if attribute.render
                 attribute.render()
               else
+                is_grouped = opinion_views_ui.group_by == attribute.key
+
+                true_false = !is_grouped && true in attribute.options && false in attribute.options && attribute.options.length == 2
+
+                if true_false
+                  options = ['true', 'false', 'either']
+                else
+                  options = attribute.options
+
                 UL null,

-                  for val in attribute.options
-                    is_grouped = opinion_views_ui.group_by == attribute.key
-                    checked = !!opinion_views_ui.visible_attribute_values[attribute.key][val]
-
-                    val_name = "#{val}"
+                  for val in options
+                    if true_false && opinion_views_ui.visible_attribute_values[attribute.key]['true'] && opinion_views_ui.visible_attribute_values[attribute.key]['false']
+                      checked = val == 'either'
+                    else
+                      checked = !!opinion_views_ui.visible_attribute_values[attribute.key][val]
+                    val_name = "#{val}"
                     shortened = false
                     if val_name.length > 25
                       val_name = "#{val_name.substring(0,22)}..."
@@ -1090,16 +1104,21 @@
                         LABEL
                           className: "attribute_value_selector"
                           title: if shortened then val
+
                           SPAN
                             className: if is_grouped then 'toggle_switch' else ''

                             INPUT
-                              type: 'checkbox'
-                              # className: 'bigger'
+                              type: if true_false then 'radio' else 'checkbox'
                               value: val
                               checked: checked
                               onChange: (e) ->
                                 # create a view on the fly for this attribute
+                                if true_false
+                                  opinion_views_ui.visible_attribute_values[attribute.key][true] = false
+                                  opinion_views_ui.visible_attribute_values[attribute.key][false] = false
+                                  opinion_views_ui.visible_attribute_values[attribute.key]["either"] = false
+
                                 opinion_views_ui.visible_attribute_values[attribute.key][val] = e.target.checked
                                 save opinion_views_ui
                                 construct_view_for_attribute(attribute)
@@ -1705,6 +1724,11 @@
     cursor: pointer;
     margin-right: 18px;
   }
+
+  .attribute_value_selector > span {
+    display: flex;
+    align-items: center;
+  }
   .attribute_value_selector input {

   }
@@ -1713,6 +1737,7 @@
     font-size: 12px;
     font-weight: 400;
     letter-spacing: -1px;
+    text-transform: capitalize;
   }

   .opinion-date-filter {


fix indentation

fix indentation

--- tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee
@@ -28,8 +28,8 @@
     DURATION: 10

   try
-  # FireFox will throw an exception if you even access the console object and it does
-  # not exist. Wow!
+    # FireFox will throw an exception if you even access the console object and it does
+    # not exist. Wow!
     nativeConsole = console
   catch e


Correctly indent context

Correctly indent context

--- lib/Default.coffee
+++ lib/Default.coffee
@@ -43,7 +43,8 @@
     return '' if @keyword
     @types[type].separator or ''

-  outputContext: -> @context or ''
+  outputContext: ->
+    " #{@context}" or ''

   transport: console.log


More oreder in default game state

More oreder in default game state

--- src/coffee/services/default_game.coffee
+++ src/coffee/services/default_game.coffee
@@ -25,28 +25,26 @@
     runnerCard1 = @faceUpCard('03036', faceUp: true) # Monolith
     runnerCard2 = @faceUpCard('02091', faceUp: true, tokens: @tokensCollection([@tokens('credits', 3)])) # Kati Jones

-    # Rows
+    # Decks

     decksArray = []
     _.times(30, => decksArray.push(@faceDownCard()))

-    firstNoCardsRow = new Monolith.ViewModels.RowViewModel()
-    lastNoCardsRow = new Monolith.ViewModels.RowViewModel()
+    # Rows

-    runnerMainRow = new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection([decksArray, runnerCard1]))
-    corpMainRow = new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection([decksArray, corpIce1, corpIce2]))
+    runnerMainRow = @row([decksArray, runnerCard1])
+    runnerIdRow   = @row([runnerId])
+    runnerTempRow = @row([runnerCard2])
+    runnerRows    = @rows([runnerTempRow, runnerIdRow, runnerMainRow, @row()])

-    runnerIdRow = new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection([runnerId]))
-    corpIdRow = new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection([corpId]))
+    corpMainRow  = @row([decksArray, corpIce1, corpIce2])
+    corpIdRow    = @row([corpId])
+    corpArchives = @row([corpTrash])
+    corpRows     = @rows([@row(), corpIdRow, corpMainRow, corpArchives])

-    corpArchives = new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection([corpTrash]))
+    # Hands

-    runnerTempRow = new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection([runnerCard2]))
-
-    runnerRows = @rows([runnerTempRow, runnerIdRow, runnerMainRow, lastNoCardsRow])
     runnerHand = @cards([runnerHand1, runnerHand2, runnerHand3, runnerHand4])
-
-    corpRows   = @rows([firstNoCardsRow, corpIdRow, corpMainRow, corpArchives])
     corpHand   = @cards([corpHand1, corpHand2, corpHand3, corpHand4, corpHand5])

     # Tokens
@@ -73,6 +71,9 @@
   @game: (options) ->
     new Monolith.ViewModels.GameViewModel(options)

+  @row: (piles = []) ->
+    new Monolith.ViewModels.RowViewModel(cardPiles: new Backbone.Collection(piles))
+
   @rows: (rows) ->
     new Backbone.Collection(rows)


modify: text kerning jquery to javascript.

--- src/common/scripts/coffee/common.coffee
+++ src/common/scripts/coffee/common.coffee
@@ -111,7 +111,6 @@
         url = 'http://line.me/R/msg/text/?'
         url += encodeURIComponent(i_text)
         url += '%20' + encodeURIComponent(i_url)
-        console.log url
         window.open url, 'share', [
           'width=550'
           'height=450'
@@ -131,58 +130,69 @@
   #------------------------------------------------------

   # CSS設定 @type {{first: string, rspace: string, rspace2: string}}
-  _defaultCSS =
+  textKerningCSS =
     first: '-.5em'
     rspace: '-.75em'
     rspace2: '-.5em'

   # カーニングのやつ @param $target
-  @textKerning: ($target) ->
-    $target.each ->
-      __$target = $(this)
+  textKerning = ($target) ->
+    Array::forEach.call document.querySelectorAll($target), (el, i) ->
+      __$target = el

       # altとtitleをいったん格納する
       __alt = []
       __title = []
-      __$img = __$target.find('img')
-      __$a = __$target.find('a')
+      __$img = __$target.getElementsByTagName('img')
+      __$a = __$target.getElementsByTagName('a')
       if __$img.length > 0
-        __$img.each ->
-          __alt.push $(this).attr('alt')
-          $(this).removeAttr 'alt'
+        Array::forEach.call __$img, (el, i) ->
+          __alt.push el.getAttribute('alt')
+          el.setAttribute 'alt', ''
           return
       if __$a.length > 0
-        __$a.each ->
-          __title.push $(this).attr('title')
-          $(this).removeAttr 'title'
+        Array::forEach.call __$a, (el, i) ->
+          __title.push el.getAttribute('title')
+          el.setAttribute 'title', ''
           return

       # もにょもにょする
-      __text = __$target.html()
+      __text = __$target.innerHTML
       __text = __text.replace(/^(\s|\t|\n)+|(\s|\t|\n)+$/g, '') #文章の頭とケツの半角・全角スペース、タブ、改行削除
       __text = __text.replace(/(\n)((\s|\t)+)/g, '') #改行後の頭の半角・全角スペース、タブ削除
       __text = __text.replace(/^(（|〔|［|｛|〈|《|「|『|【)/g, '$1') #文頭調整
       __text = __text.replace(/(
|
)(（|〔|［|｛|〈|《|「|『|【)/g, '$1$2') #br改行後の文頭調整
       __text = __text.replace(/(、|。|，|．|）|〕|］|｝|〉|》|」|』|】)(、|。|，|．|）|〕|］|｝|〉|》|」|』|】)(（|〔|［|｛|〈|《|「|『|【)/g, '$1$2$3')
       __text = __text.replace(/(、|。|，|．|）|〕|］|｝|〉|》|」|』|】)(（|〔|［|｛|〈|《|「|『|【)/g, '$1$2')
-      __$target.html __text
-      __$target.find('.rspace-first').css
-        position: 'relative'
-        left: _defaultCSS.first
-        letterSpacing: _defaultCSS.first
-      __$target.find('.rspace').css letterSpacing: _defaultCSS.rspace
-      __$target.find('.rspace2').css letterSpacing: _defaultCSS.rspace2
+      __$target.innerHTML = __text
+      target = __$target.querySelectorAll('.rspace-first')
+      target2 = __$target.querySelectorAll('.rspace')
+      target3 = __$target.querySelectorAll('.rspace2')
+      i = 0
+      while i < target.length
+        target[i].style.position = 'relative'
+        target[i].style.left = textKerningCSS.first
+        target[i].style.letterSpacing = textKerningCSS.first
+        i++
+      i = 0
+      while i < target2.length
+        target2[i].style.letterSpacing = textKerningCSS.rspace
+        i++
+      i = 0
+      while i < target3.length
+        target3[i].style.letterSpacing = textKerningCSS.rspace2
+        i++

       # altとtitleつけなおす
-      __$img = __$target.find('img')
-      __$a = __$target.find('a')
+      __$img = __$target.getElementsByTagName('img')
+      __$a = __$target.getElementsByTagName('a')
       if __$img.length > 0
-        __$img.each (i) ->
-          $(this).attr 'alt': __alt[i]
+        Array::forEach.call __$img, (el, i) ->
+          el.setAttribute 'alt', __alt[i]
           return
       if __$a.length > 0
-        __$a.each (i) ->
-          $(this).attr 'title': __title[i]
+        Array::forEach.call __$a, (el, i) ->
+          el.setAttribute 'title', __title[i]
           return

       return


Only delegate events when necessary

Only delegate events when necessary

--- scripts/views/workspace/menu/toolbar-search.coffee
+++ scripts/views/workspace/menu/toolbar-search.coffee
@@ -5,6 +5,8 @@
   'marionette'
   'hbs!templates/workspace/menu/toolbar-search'
 ], ($, _, Backbone, Marionette, toolbarTemplate) ->
+
+  _rendered = false

   return new (Marionette.ItemView.extend
     template: toolbarTemplate
@@ -14,8 +16,10 @@
       'keyup .search-query': 'search'

     onRender: () ->
-      # Make sure we have delegated events
-      @delegateEvents()
+      # Marionette won't re-delegate events if we close this view
+      # and then re-show it, so do it here.
+      if _rendered then @delegateEvents()
+      _rendered = true

     search: (e) ->
       @trigger('search', $(e.currentTarget).val())


It ain't love but it ain't bad: Reverse DNS Functional

--- src/scripts/reverse-dns.coffee
+++ src/scripts/reverse-dns.coffee
@@ -15,4 +15,8 @@

 module.exports = (robot) ->
   robot.respond /reverse dns (.*)/i, (msg) ->
-    # Get reverse dns
+    ip = msg.match[1].toLowerCase()
+    robot.http("http://api.hackertarget.com/reversedns/?q=#{ip}")
+      .get() (err, res, body) ->
+
+        msg.send "Reverse DNS: #{body}"


(#110) Fix buildErrs exception on Unix and Darwin

--- lib/builder.coffee
+++ lib/builder.coffee
@@ -63,7 +63,7 @@
       @buildLogs[@buildLogs.length - 1] += data

     @currentProcess.stderr.on 'data', (data) =>
-      @buildErrs += data
+      @buildErrs[@buildErrs.length - 1] += data

     @currentProcess.on 'error', (err) =>
       # Fatal executable error


add super basic text wrapping

add super basic text wrapping

still to do: increase rectangle width and height as text grows

--- dramagrama.coffee
+++ dramagrama.coffee
@@ -51,7 +51,6 @@

 		# <3 coffescript
 		parsedBits.push(@parseLine line) for line in allTheLines;
-
 		return @parseTree parsedBits

 	parseTree: (parsedBits) ->
@@ -66,9 +65,7 @@
 			b = @findNodeInTree bname, tree;

 			# do we need to add the parent block to the tree?
-			if !a
-				a = {name: aname, colour: "", children:[]}
-				tree.children.push a
+			tree.children.push a = {name: aname, colour: "", children:[]} if !a

 			# don't panic about self loops
 			continue if aname == bname
@@ -85,8 +82,8 @@
 					tree.children[tree.children.indexOf(b)] = null

 			# if the colours or arrow have updated, save them
-			if a
-				a.colour = bit.first.colour if bit.first.colour
+			a.colour = bit.first.colour if a && bit.first.colour
+
 			if b
 				b.colour = bit.second.colour if bit.second.colour
 				b.arrow = bit.arrow if bit.arrow
@@ -237,6 +234,7 @@
 	constructor: (@paper) ->
 		@paper.clear()
 		@rectangleWidth = 100
+		@rectangleMaxWidth = 100
 		@rectangleHeight = 50
 		@childrenVerticalPadding = 40
 		@childrenHorizontalPadding = 20
@@ -277,11 +275,27 @@

 		return {rectangle:newRect, svg:actualRect}

-	drawText: (x, y, text) ->
-		@paper.text(x, y, text).attr(
+	drawText: (x, y, content) ->
+		maxWidth = @rectangleWidth
+		t = @paper.text(x, y).attr(
 			{"font-size": "13px",
-			"font-family":"'Shadows Into Light Two', sans-serif"
+			"font-family":"'Shadows Into Light Two', sans-serif",
+			"text-anchor": "center"
 			})
+
+		# thanks stack overflow
+		words = content.split(" ")
+		tempText = ""
+		for word in words
+			t.attr("text", tempText + " " + word)
+			if (t.getBBox().width > maxWidth)
+				tempText += "\n" + word
+			else
+				tempText += " " + word
+
+		t.attr("text", tempText.substring(1))
+
+

 	drawAndConnectToBlock:(previousBlock, block, childIndex, direction, arrow) ->
 		previousRectangle = previousBlock.rectangle # block also contains the svg, just in case


app-settings-menu: Passed menu offset to child item callback method.

--- client/app/MainApp/maincontroller/mainviews/appsettingsmenubutton.coffee
+++ client/app/MainApp/maincontroller/mainviews/appsettingsmenubutton.coffee
@@ -14,20 +14,19 @@
         item.callback = (contextmenu) =>
           view = mainTabView.activePane?.mainView
           item.eventName or= item.title
-          view?.emit "menu.#{item.eventName}", item.eventName, item, contextmenu
           @contextMenu.destroy()
+          view?.emit "menu.#{item.eventName}", item.eventName, item, contextmenu, @offset

       @createMenu event, menu

     super options, data

   createMenu: (event, menu) ->
-    offset = @$().offset()
+    @offset = @$().offset()
     @contextMenu = new JContextMenu
-      event       : event
       delegate    : @
-      x           : offset.left - 150
-      y           : offset.top + 20
+      x           : @offset.left - 150
+      y           : @offset.top + 20
       arrow       :
         placement : "top"
         margin    : -5


Improve debug server eval.

Improve debug server eval.

--- src/server/debug_server.coffee
+++ src/server/debug_server.coffee
@@ -48,7 +48,8 @@
             browser = null
             if browserID == 'global'
                 socket.on 'evaluate', (cmd) ->
-                    eval.call(global, cmd)
+                    do () ->
+                        eval(cmd)
                 return
             for val, idx in global.browserList()
                 if val.id == browserID


execute CSSComb on Save

execute CSSComb on Save

--- lib/csscomb.coffee
+++ lib/csscomb.coffee
@@ -2,6 +2,7 @@

 fs          = require('fs')
 path        = require('path')
+
 CSSBeautify = require('cssbeautify')
 CSSComb     = require('csscomb')

@@ -10,16 +11,25 @@

 module.exports =

+  config:
+    executeOnSave:
+      title: 'Execute on save'
+      description: 'Execute sorting CSS property on save.'
+      type: 'boolean'
+      default: false
+
   activate: (state) ->
-    atom.commands.add 'atom-workspace', 'csscomb:execute', => @execute()
+    atom.commands.add 'atom-workspace', 'csscomb:execute', () => @execute()
+    @editorObserver = atom.workspace.observeTextEditors (editor) =>
+      editor.getBuffer().onWillSave () =>
+        if (@isExecuteOnSave())
+          @execute()

   deactivate: ->
+    @editorObserver.dispose()

-  getExecPath: ->
-    "ATOM_SHELL_INTERNAL_RUN_AS_NODE=1 '#{process.execPath}'"
-
-  getNodePath: ->
-    atom.config.get('csscomb.nodepath')
+  isExecuteOnSave: ->
+    atom.config.get('atom-csscomb.executeOnSave')

   execute: ->
     editor = atom.workspace.getActiveTextEditor()
@@ -35,12 +45,14 @@
     grammarName = editor.getGrammar().name.toLowerCase()
     isCSS  = grammarName is 'css'
     isScss = grammarName is 'scss'
+    isSass = grammarName is 'sass'
     isLess = grammarName is 'less'
     isHTML = grammarName is 'html'

     syntax = 'css'
     if isCSS  then syntax = 'css'
     if isScss then syntax = 'scss'
+    if isSass then syntax = 'sass'
     if isLess then syntax = 'less'
     if isHTML then syntax = 'css'

@@ -52,10 +64,12 @@
         sorted = csscomb.processString(selected, {
           syntax: syntax
         })
+
         if isCSS
           sorted = CSSBeautify(sorted, {
             indent: '  '
           })
+
         editor.setTextInBufferRange(editor.getSelectedBufferRange(), sorted)
       catch e
         console.log(e)


when a syncdoc fails to save to disk, do a reconnect, which should fix any issue with potentially dropped changes.   This should never happen, but is a safe way to quickly recover since in practice it sometimes does.

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -1771,7 +1771,14 @@
             misc.retry_until_success
                 f         : f
                 max_tries : 5
-                cb        : cb
+                cb        : (err) =>
+                    if err
+                        # TODO: This should in theory never be necessary, and I've resisted adding
+                        # it for five years.  However, here it is:
+                        console.warn("'#{@_path}': failed to save to disk; initiating syncstring reconnect")
+                        @reconnect (err) =>
+                            console.warn("'#{@_path}': reconnect got ", err)
+                    cb(err)

     # Save this file to disk, if it is associated with a project and has a filename.
     # A user (web browsers) sets the save state to requested.


improve upgrades UI

improve upgrades UI

--- src/smc-webapp/r_upgrades.cjsx
+++ src/smc-webapp/r_upgrades.cjsx
@@ -78,7 +78,7 @@
                         {{u} {misc.plural(u, info.display_unit)} if u?}
                     </Col>
                     <Col sm={7}>
-                        <ProgressBar striped now={percent_used} style={marginBottom: '0px', backgroundColor:'orange'}/>
+                        <ProgressBar striped now={percent_used} style={margin: '3px 0px', border:'1px solid grey'}/>
                     </Col>
                 </Row>
             </Col>
@@ -108,7 +108,7 @@
         # a project nobody has touched for a month, which has upgrades applied to it.
         @props.redux.getActions('projects').load_all_projects()

-        <Panel header={
Upgrades from your subscriptions and course packages. Unallocated upgrades are shown in orange.
}>
+        <Panel header={
Upgrades from your subscriptions and course packages.
}>
             <Row key='header'>
                 <Col sm={2}>
                     Quota


hammer implementation

hammer implementation

--- src/js/Explorator.coffee
+++ src/js/Explorator.coffee
@@ -18,11 +18,13 @@
     @calc()
     @h = new Hammer img

+    @h.get('pinch').set({ enable: true });
+    @h.get('rotate').set({ enable: true });

     @h.on 'tap', @onTap
+    @h.on 'pinch', @onPinch
+    @h.on 'rotate', @onRotate
     @h.on 'pan', @onPan
-    @h.on 'rotate', @onRotate
-    @h.on 'pinch', @onPinch
     @h.on 'panstart rotatestart pinchstart', @resetStart
     @h.on 'panend rotateend pinchend pancancel rotatecancel pinchcancel', @resetEnd

@@ -49,23 +51,29 @@
             abscureBox.showPrev()
         else if @panDirection is 2
           abscureBox.hide()
-      else
+      else if @transform.scale is 1
+        @__transform.translate.x = @transform.translate.x
+        @__transform.translate.y = @transform.translate.y
         @align no
       @panDirection = null
-      return @pinched = no if @pinched
-      @__transform.translate.x = @transform.translate.x
-      @__transform.translate.y = @transform.translate.y
-
+      # return @pinched = no if @pinched
+      # if if @transform.scale is 1
+
+    else if e and e.type in ['pinchend', 'pinchcancel']
+      @__transform.scale = @transform.scale
+
+    @transform.rotate = 0
     @$img.addClass 'animate'
     @requestUpdate()

-  onPinch: (ev) ->
-    transform.scale = _scale * ev.scale
-    requestElementUpdate()
-
-  onRotate: (ev) ->
-    transform.rotate = ev.rotation
-    requestElementUpdate()
+  onPinch: (e) ->
+    @pinched = yes
+    @transform.scale = @__transform.scale * e.scale
+    @requestUpdate()
+
+  onRotate: (e) ->
+    @transform.rotate = e.rotation
+    @requestUpdate()

   onPan: (e) ->
     if @transform.scale is 1


return empty array on unsuccessful search (userful in case no data)

--- src/leaflet.timeline.coffee
+++ src/leaflet.timeline.coffee
@@ -45,7 +45,7 @@
     if node == undefined
       node = @_root
       @_list = []
-    if node == null or node.max < value then return
+    if node == null or node.max < value then return []
     if node.left != null then @lookup value, node.left
     if node.low <= value
       if node.high >= value then @_list.push node.data
@@ -163,7 +163,7 @@
     @showTicks = @timeline.options.showTicks
     @stepDuration = @timeline.options.duration / @timeline.options.steps
     @stepSize = ( @end - @start ) / @timeline.options.steps
-
+
   _buildDataList: (container, times) ->
     @_datalist = L.DomUtil.create 'datalist', '', container
     datalistSelect = L.DomUtil.create 'select', '', @_datalist
@@ -241,7 +241,7 @@
     clearTimeout @_timer
     if +@_timeSlider.value == @end then @_timeSlider.value = @start
     @_timeSlider.stepUp @stepSize
-    @_sliderChanged
+    @_sliderChanged
       type: 'change'
       target: value: @_timeSlider.value
     unless +@_timeSlider.value == @end


use oauth1 style params

use oauth1 style params

--- app/config.coffee
+++ app/config.coffee
@@ -8,13 +8,12 @@
   consumerKey: process.env.TWITTER_CLIENT_ID
   consumerSecret: process.env.TWITTER_CLIENT_SECRET
   callbackURL: process.env.TWITTER_CALLBACK_URL
-  passReqToCallback: true

 class TwitterConfig
   constructor: (@meshbluConn, @meshbluJSON) ->
     @meshbludb = new MeshbluDB @meshbluConn

-  onAuthentication: (request, accessToken, refreshToken, profile, done) =>
+  onAuthentication: (accessToken, refreshToken, profile, done) =>
     profileId = profile?.id
     fakeSecret = 'twitter-authenticator'
     authenticatorUuid = @meshbluJSON.uuid


Fix SQLite error stack traces for item API

--- api/routes/items.coffee
+++ api/routes/items.coffee
@@ -90,7 +90,7 @@
                     , (err) ->
                         if err?
                             console.log err
-                            console.stack()
+                            console.trace()

             stmtAdd = db.prepare '''INSERT INTO tItemCache (id, build, fromapi, name, icon, description, type, rarity,
                       level, vendorValue, flags, restrictedTo, chatLink, detailsObject)
@@ -104,7 +104,7 @@
                     , (err) ->
                         if err?
                             console.log err
-                            console.stack()
+                            console.trace()

             stmtUpdate.finalize()
             stmtAdd.finalize()


Fix lingering popup problem.

Fix lingering popup problem.

--- src/map-base-view.coffee
+++ src/map-base-view.coffee
@@ -238,9 +238,11 @@
                     _popup = createdPopup
                 else
                     _popup = popup
-                if _popup? and not _popup.selected
-                    @popups.removeLayer _popup
-                prevent = true
+                if _popup?
+                    if _popup.marker?.unit == @selectedUnits.first()
+                        prevent = true
+                    else
+                        @popups.removeLayer _popup
                 _.delay (=> prevent = false), delay

             marker.on hideEvent, popupOff


sprite docs

sprite docs

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -74,10 +74,19 @@
       return window.Sprite.fromURL(name, callback)

   ###*
-  A constant sprite that draws nothing.
+  A sprite that draws nothing.

   @name EMPTY
   @fieldOf Sprite
+  @constant
+  @type Sprite
+  ###
+  ###*
+  A sprite that draws nothing.
+
+  @name NONE
+  @fieldOf Sprite
+  @constant
   @type Sprite
   ###
   window.Sprite.EMPTY = window.Sprite.NONE = LoaderProxy()


removed a temp file

removed a temp file

--- vendor/assets/javascripts/mercury/image_processor.js.coffee
+++ vendor/assets/javascripts/mercury/image_processor.js.coffee
@@ -1,26 +0,0 @@
-class @Mercury.ImageProcessor extends Mercury.Dialog
-
-  constructor: ->
-    # load file etc
-    super
-
-
-  build: ->
-    @overlay = jQuery('<div>', {class: "mercury-image-processor-overlay loading", style: 'display:none'})
-    @element = jQuery('<div>', {class: "mercury-image-processor loading", style: 'display:none'})
-
-    @overlay.appendTo(jQuery(@options.appendTo).get(0) ? 'body')
-    @element.appendTo(jQuery(@options.appendTo).get(0) ? 'body')
-
-
-  bindEvents: ->
-    super
-    # bind to interface elements
-
-
-  resize: ->
-    super
-
-  position: (keepVisible) ->
-    ''
-    #@element.css({top: 20, left: 20, bottom: 20, right: 20})


activity feed: fix bug where last signed up member wasn't showing up

--- workers/social/lib/social/models/graph/decorators/newmemberbucket.coffee
+++ workers/social/lib/social/models/graph/decorators/newmemberbucket.coffee
@@ -37,10 +37,9 @@
     return members

   addToOverview:(member)->
+    return  if @overview.count > 3
+
     @overview.count++
-
-    return  if @overview.count > 5
-
     @overview.createdAt.unshift member.meta.createdAt
     @overview.ids.unshift member.id


C-ROC

C-ROC

Translated all the cards from the C-ROC expansion.

I have a doubt with the "Quick-release Cargo Locks" tho. Usually I would add the Ship: tag so it won't go missing due the translated ship name, but in this case is an upgrade for *two* ships, C-ROC and GR-75. How must I put this on these cases? Two regular Ship: tags, one for each ship?

Right now I didn't add an tag on it.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -84,6 +84,7 @@
         "Upsilon-class Shuttle Expansion Pack": "Pack de Expansión Lanzadera clase Ípsilon"
         "Sabine's TIE Fighter Expansion Pack": "Pack de Expansión Caza TIE de Sabine"
         "Quadjumper Expansion Pack": "Pack de Expansión Saltador Quad"
+        "C-ROC Cruiser Expansion Pack": "Pack de Expansión Crucero C-ROC"
     ui:
         shipSelectorPlaceholder: "Selecciona una nave"
         pilotSelectorPlaceholder: "Selecciona un piloto"
@@ -219,6 +220,7 @@
     exportObj.renameShip 'Protectorate Starfighter', 'Caza Estelar del Protectorado'
     exportObj.renameShip 'Lancer-class Pursuit Craft', 'Nave de persecución clase Lancero'
     exportObj.renameShip 'Quadjumper', 'Saltador Quad'
+    exportObj.renameShip 'C-ROC Cruiser', 'Crucero C-ROC'

     pilot_translations =
         "Wedge Antilles":
@@ -546,7 +548,7 @@
             text: """Al comienzo de la fase de Combate, si tienes alguna nave enemiga a alcance 1 puedes asignar 1 ficha de Concentración a tu nave."""
             ship: "Víbora Estelar"
         "Cartel Spacer":
-            name: "Agente del Cartel"
+            name: "Agente del Cártel"
             ship: "Interceptor M3-A"
         "Tansarii Point Veteran":
             name: "Veterano de Punto Tansarii"
@@ -976,16 +978,19 @@
             name: "Traficante de armas de Jakku"
         'Genesis Red':
             ship: "Interceptor M3-A"
-            text: '''After you acquire a target lock, assign focus and evade tokens to your ship until you have the same number of each token as the locked ship.'''
+            text: '''Después de que fijes un blanco, asigna fichas de Concentración y fichas de Evasión a tu nave hasta que tengas tantas fichas de cada tipo como la nave que has fijado.'''
         'Quinn Jast':
             ship: "Interceptor M3-A"
-            text: '''At the start of the Combat phase, you may receive a weapons disabled token to flip one of your discarded %TORPEDO% or %MISSILE% Upgrade cards faceup.'''
+            text: '''Al comienzo de la fase de Combate, puedes recibir una ficha de Armas inutilizadas para poner boca arriba una de tus cartas de Mejora %TORPEDO% o %MISSILE% descartadas.'''
         'Inaldra':
             ship: "Interceptor M3-A"
-            text: '''When attacking or defending, you may spend 1 shield to reroll any number of your dice.'''
+            text: '''Cuando ataques o te defiendas, puedes gastar 1 ficha de Escudos para volver a tirar cualquier cantidad de tus dados.'''
         'Sunny Bounder':
             ship: "Interceptor M3-A"
-            text: '''Once per round, after you roll or reroll dice, if you have the same result on each of your dice, add 1 matching result.'''
+            text: '''Una vez por ronda, después de que tires o vuelvas a tirar los dados, si has sacado el mismo resultado en cada uno de tus dados, puedes añadir 1 más de esos resultados a la tirada.'''
+        'C-ROC Cruiser':
+            ship: "Crucero C-ROC"
+            name: "Crucero C-ROC"
         'Lieutenant Kestal':
             text: '''When attacking, you may spend 1 focus token to cancel all of the defender's blank and %FOCUS% results.'''
         '"Double Edge"':
@@ -1622,17 +1627,22 @@
             text: '''Cuando una nave con la que estás en contacto se activa, puedes mirar la maniobra que ha elegido. Si lo haces, el jugador que controla esa nave debe elegir en el selector una maniobra adyacente. La nave puede revelar y efectuar esa maniobra incluso aunque esté bajo tensión.'''
         # C-ROC
         'Heavy Laser Turret':
-            text: '''C-ROC Cruiser only.%LINEBREAK%Attack (energy): Spend 2 energy from this card to perform this attack against 1 ship (even a ship outside of your firing arc).'''
+            ship: "Crucero C-ROC"
+            name: "Torreta de láser pesado"
+            text: '''Sólo crucero C-ROC%LINEBREAK%Ataque (energía): Gasta 2 de Energía de esta carta para efectuar este ataque contra 1 nave (incluso contra una nave fuera de tu arco de fuego).'''
         'Cikatro Vizago':
-            text: '''%SCUMONLY%%LINEBREAK%At the start of the End phase, you may discard this card to replace a faceup %ILLICIT% or %CARGO% Upgrade card you have equipped with another Upgrade card of the same type of equal or fewer squad points.'''
+            text: '''%SCUMONLY%%LINEBREAK%Al comienzo de la fase Final, puedes descartar esta carta pare reemplazar una carta de Mejora %ILLICIT% o %CARGO% que tengas equipada boca arriba por otra carta de Mejora de ese mismo tipo con un coste en puntos de escuadrón igual o inferior.'''
         'Azmorigan':
-            text: '''%HUGESHIPONLY% %SCUMONLY%%LINEBREAK%At the start of the End phase, you may spend 1 energy to replace a faceup %CREW% or %TEAM% Upgrade card you have equipped with another Upgrade card of the same type of equal or fewer squad points.'''
+            text: '''%HUGESHIPONLY% %SCUMONLY%%LINEBREAK%Al comienzo de la fase Final, puedes gastar 1 de Energía para reemplazar una carta de Mejora %CREW% o %TEAM% que tengas equipada boca arriba por otra carta de Mejora de ese mismo tipo con un coste en puntos de escuadrón igual o inferior.'''
         'Quick-release Cargo Locks':
-            text: '''%LINEBREAK%At the end of the Activation phase, you may discard this card to place 1 container token.'''
+            name: "Enganches de carga de apertura rápida"
+            text: '''%LINEBREAK%Al final de la fase de Activación puedes descartar esta carta para colocar 1 indicador de Contenedores.'''
         'Supercharged Power Cells':
-            text: '''When attacking, you may discard this card to roll 2 additional attack dice.'''
+            name: "Células de energía sobrealimentadas"
+            text: '''Cuando ataques, puedes descartar esta carta para tirar 2 dados de ataque adicionales.'''
         'ARC Caster':
-            text: '''Rebel and Scum only.%DUALCARD%%LINEBREAK%Side A:%LINEBREAK%Attack: Attack 1 ship.  If this attack hits, you must choose 1 other ship at Range 1 of the defender to suffer 1 damage.%LINEBREAK%Then flip this card.%LINEBREAK%Side B:%LINEBREAK%(Recharging) At the start of the Combat phase, you may receive a weapons disabled token to flip this card.'''
+            name: "Proyector ARC"
+            text: '''Sólo Escoria y Rebelde.%DUALCARD%%LINEBREAK%Cara A:%LINEBREAK%Ataque: Ataca a 1 nave. Si este ataque impacta, debes elegir 1 otra nave a alcance 1 del defensor para que sufra 1 punto de daño.%LINEBREAK%Luego dale la vuelta a esta carta.%LINEBREAK%Cara B:%LINEBREAK%(Recargándose) Al comienzo de la fase de Combate, puedes recibir una ficha de Armas inutilizadas para darle la vuelta a esta carta.'''
         'Wookiee Commandos':
             text: '''When attacking, you may reroll your %FOCUS% results.'''
         'Synced Turret':
@@ -1642,9 +1652,11 @@
         'Intensity':
             text: '''%SMALLSHIPONLY% %DUALCARD%%LINEBREAK%Side A: After you perform a boost or barrel roll action, you may assign 1 focus or evade token to your ship. If you do, flip this card.%LINEBREAK%Side B: (Exhausted) At the end of the Combat phase, you may spend 1 focus or evade token to flip this card.'''
         'Jabba the Hutt':
-            text: '''%SCUMONLY%%LINEBREAK%When you equip this card, place 1 illicit token on each %ILLICIT% Upgrade card in your squad.  When you are instructed to discard an Upgrade card, you may discard 1 illicit token on that card instead.'''
+            name: "Jabba el Hutt"
+            text: '''%SCUMONLY%%LINEBREAK%Cuando equipes esta carta, coloca 1 ficha de Material Ilícito encima de cada carta de Mejora %ILLICIT% en tu escuadrón. Cuando debas descartar una carta de Mejora, en vez de eso puedes descartar 1 ficha de Material ilícito que esté encima de esa carta.'''
         'IG-RM Thug Droids':
-            text: '''When attacking, you may change 1 of your %HIT% results to a %CRIT% result.'''
+            name: "Droides matones IG-RM"
+            text: '''Cuando ataques, puedes cambiar 1 de tus resultados %HIT% por un resultado %CRIT%.'''
         'Selflessness':
             text: '''%SMALLSHIPONLY% %REBELONLY%%LINEBREAK%When a friendly ship at Range 1 is hit by an attack, you may discard this card to suffer all uncanceled %HIT% results instead of the target ship.'''
         'Breach Specialist':
@@ -1724,9 +1736,11 @@
             name: "Droide Astromecánico Integrado"
             text: '''Solo X-wing.%LINEBREAK%Cuando recibas una carta de Daño, puedes descartar 1 de tus cartas de Mejora %ASTROMECH% para descartar esa carta de Daño (sin resolver su efecto).'''
         'Optimized Generators':
-            text: '''%HUGESHIPONLY%%LINEBREAK%Once per round, when you assign energy to an equipped Upgrade card, gain 2 energy.'''
+            name: "Generadores optimizados"
+            text: '''%HUGESHIPONLY%%LINEBREAK%Una vez por ronda, cuando asignes Energía a una carta de Mejora equipada, obtienes 2 de Energía.'''
         'Automated Protocols':
-            text: '''%HUGESHIPONLY%%LINEBREAK%Once per round, after you perform an action that is not a recover or reinforce action, you may spend 1 energy to perform a free recover or reinforce action.'''
+            name: "Procedimientos automatizados"
+            text: '''%HUGESHIPONLY%%LINEBREAK%OUna vez por ronda, después de que realices una acción que no sea una acción de recuperación o de refuerzo, puedes gastar 1 de Energía para realizar una acción gratuita de recuperación o de refuerzo.'''
         'Ordnance Tubes':
             text: '''%HUGESHIPONLY%%LINEBREAK%You may treat each of your %HARDPOINT% upgrade icons as a %TORPEDO% or %MISSILE% icon.%LINEBREAK%When you are instructed to discard a %TORPEDO% or %MISSILE% Upgrade card, do not discard it.'''
         'Long-Range Scanners':
@@ -1757,7 +1771,8 @@
             name: "Fuselaje ultraligero"
             text: '''Sólo TIE.%LINEBREAK%Cuando te defiendas, tras tirar los dados de defensa, si hay más dados de ataque que dados de defensa, tira 1 dado de defensa adicional.%LINEBREAK%Esta mejora no puede equiparse en naves con puntuación de Agilidad 3 o superior.'''
         'Pulsed Ray Shield':
-            text: '''Rebel and Scum only.%LINEBREAK%During the End phase, you may receive 1 ion token to recover 1 shield (up to your shield value). You can equip this card only if your shield value is "1."'''
+            name: "Escudo de rayos pulsátil"
+            text: '''Sólo Escoria y Rebelde.%LINEBREAK%Durante la fase Final, puedes recibir 1 ficha de Iones para recuperar 1 ficha de Escudos (pero no puedes exceder tu valor de Escudos). Sólo puedes equipar esta carta si tu valor de Escudos es 1.'''

     title_translations =
         "Slave I":
@@ -1927,14 +1942,21 @@
             text: '''Sólo Fustigador TIE.%LINEBREAK%Inmediatamente antes de revelar tu selector de maniobras, si no estás bajo tensión, debes ejecutar una manibora blanca (%BANKLEFT% 1), (%STRAIGHT% 1) o (%BANKRIGHT% 1).'''
         # C-ROC
         '''Merchant One''':
-            text: '''C-ROC Cruiser only.%LINEBREAK%Your upgrade bar 1 additional %CREW% upgrade icon and 1 additional %TEAM% upgrade icon and loses 1 %CARGO% upgrade icon.'''
+            name: "Mercader Uno"
+            ship: "Crucero C-ROC"
+            text: '''Sólo C-ROC Cruiser.%LINEBREAK%Tu barra de mejoras gana 1 icono %CREW% adicional y 1 icono %TEAM% adicional y pierde 1 icono %CARGO%.'''
         '''"Light Scyk" Interceptor''':
+            name: 'Interceptor "Scyk Ligero"'
             ship: "Interceptor M3-A"
-            text: '''M3-A Interceptor only.%LINEBREAK%All Damage cards dealt to you are dealt faceup.  You may treat all bank maneuvers (%BANKLEFT% or %BANKRIGHT%) as green maneuvers.  You cannot equip Modification upgrades.'''
+            text: '''Sólo Interceptor M3-A.%LINEBREAK%Toda slas cartas de Daño que te inflijan se te asignan boca arriba. Puedes ejecutar todas las maniobras de inclinación (%BANKLEFT% o %BANKRIGHT%) como maniobras verdes. No puedes equiparte con mejoras de Modificación.s.'''
         '''Insatiable Worrt''':
-            text: '''After you perform the recover action, gain 3 energy.'''
+            name: "Worrt insaciable"
+            ship: "Crucero C-ROC"
+            text: '''Después de que realices la acción de recuperación, ganas 3 de energía.'''
         '''Broken Horn''':
-            text: '''When defending, if you have a reinforce token, you may add 1 additional %EVADE% result.  If you do, after defending, discard your reinforce token.'''
+            name: "Cuerno Roto"
+            ship: "Crucero C-ROC"
+            text: '''Cuando te defiendas, si tienes asignada una ficha de Refuerzo, puedes añadir 1 resultado %EVADE% adicional.  Si lo haces, tras defenderte, descarta tu ficha de Refuerzo.'''
         'Havoc':
             text: '''Scurrg H-6 Bomber only.%LINEBREAK%Your upgrade bar gains the %SYSTEM% and %SALVAGEDASTROMECH% icons and loses the %CREW% upgrade icon.%LINEBREAK%You cannot equip non-unique %SALVAGEDASTROMECH% Upgrade cards.'''
         'Vaksai':


Add more data to object shrinking table

Add more data to object shrinking table

--- app/shrink.coffee
+++ app/shrink.coffee
@@ -1,5 +1,16 @@
 # Lookup table for compressing data
 tableTo =
+
+  # User
+  email:      "@"
+  password:   "X"
+  has_pro:    "$"
+  created_at: ":"
+  updated_at: "#"
+
+  data_Task: "dT"
+  data_List: "dL"
+  data_Time: "dX"

   # Class names
   Task: "T"
@@ -7,15 +18,15 @@
   Time: "M"

   # Properties
-  name: "n"
+  name:      "n"
   completed: "c"
-  id: "i"
-  priority: "p"
-  date: "d"
-  deleted: "x"
-  notes: "o"
-  tasks: "t"
-  list: "l"
+  id:        "i"
+  priority:  "p"
+  date:      "d"
+  deleted:   "x"
+  notes:     "o"
+  tasks:     "t"
+  list:      "l"

 tableFrom = {}


remove y label

remove y label

--- web/site/src/javascript/NvWrapper.coffee
+++ web/site/src/javascript/NvWrapper.coffee
@@ -10,11 +10,11 @@
     chart = nv.models.lineChart()
     chart.useInteractiveGuideline?(true)
     chart.xAxis
-      #.axisLabel 'Time'
+      .axisLabel ''
       .tickFormat (d) ->
         (d3.time.format '%I:%M %p') new Date d
     chart.yAxis
-    #  .axisLabel 'Temp (C)'
+      .axisLabel ''
       .tickFormat d3.format '.2f'
     # chart.color (d) -> getColor d.key
     nv.utils.windowResize(chart.update)
@@ -30,7 +30,7 @@
       return '%a'       if domain_in_days < 8
       return '%e %b'
     @chart.xAxis.tickFormat (d) -> (d3.time.format format) new Date d
-    @chart.yAxis.axisLabel data[0].key
+    #@chart.yAxis.axisLabel data[0].key
     (if typeof @target is 'string' then d3.select(@target) else @target)
       .datum data
       .call @chart


add a new function called update_question

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -17,3 +17,7 @@
   random_question = (data) ->
     choice = Math.floor(Math.random() * data['words'].length)
     return data['words'][choice]
+
+  update_question = (data) ->
+    word = random_question(data)
+    $("p#hint").empty().append(word['hint'])


operations: RetrieveItems.retrieveSubscriptionsItems() result set sorting, affiliations

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -502,11 +502,26 @@
                 results.push
                     id: followee
                     subscriptions: followeeSubscriptions
-            console.log {results}
+            # Sort for a stable traversal with multiple RSM'ed queries
+            results.sort (result1, result2) ->
+                if result1.id < result2.id
+                    -1
+                else if result1.id > result2.id
+                    1
+                else
+                    0
             # Apply RSM
             results = @req.rsm.cropResults results, 'id'
-            # TODO: get affiliations per node
-            cb2 null, results
+
+            # get affiliations per node
+            async.forEachSeries results, (result, cb3) =>
+                async.forEach result.subscriptions, (subscription, cb4) =>
+                    t.getAffiliation subscription.node, @subscriptionsNodeOwner, (err, affiliation) ->
+                        subscription.affiliation ?= affiliation
+                        cb4 err
+                , cb3
+            , (err) ->
+                cb2 err, results
         , (results, cb2) =>
             # Transform to specified items format
             for item in results
@@ -515,11 +530,12 @@
                         'xmlns:pubsub': NS.PUBSUB
                     )
                 for subscription in item.subscriptions
-                    item.el.c('item',
+                    itemAttrs =
                         jid: @req.me
                         node: subscription.node
-                        'pubsub:subscription': subscription.subscription
-                    )
+                    itemAttrs['pubsub:subscription'] ?= subscription.subscription
+                    itemAttrs['pubsub:affiliation'] ?= subscription.affiliation
+                    item.el.c('item', itemAttrs)
                 delete item.subscriptions

             results.rsm = @req.rsm


operations: access control fixes

operations: access control fixes

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -86,20 +86,13 @@

     transaction: (t, cb) ->
         async.waterfall [ (cb2) =>
-            console.log 'fetchActorAffiliation'
             @fetchActorAffiliation t, cb2
         , (cb2) =>
-            console.log 'fetchNodeConfig'
             @fetchNodeConfig t, cb2
         , (cb2) =>
-            console.log 'checkRequiredAffiliation'
             @checkRequiredAffiliation t, cb2
         , (cb2) =>
-            console.log 'checkAccessModel'
             @checkAccessModel t, cb2
-        , (cb2) =>
-            console.log 'checkAccess'
-            @checkAccess t, cb2
         ], (err) =>
             if err
                 return cb err
@@ -112,7 +105,6 @@

         # TODO: actor no user? check if listener!
         t.getAffiliation @req.node, @req.actor, (err, affiliation) =>
-            console.log 'getAffiliation ->', err, affiliation
             if err
                 return cb err

@@ -124,7 +116,7 @@
         unless @req.node
             return cb()

-        t.getConfig @req.node, (err, config) ->
+        t.getConfig @req.node, (err, config) =>
             if err
                 return cb err

@@ -253,7 +245,6 @@
     # TODO: overwrite @run() and check if this component is
     # authoritative for the requesting user's domain
     transaction: (t, cb) ->
-        console.log 'Register cb': cb
         user = @req.actor
         listener = @req.sender
         async.parallel(for own nodeType, config of defaultConfiguration(user)
@@ -298,7 +289,7 @@

 class Publish extends PrivilegedOperation
-    requiredAffiliation: 'publisher'
+    # checks affiliation with @checkPublishModel below

     privilegedTransaction: (t, cb) ->
         async.waterfall [ (cb2) =>


Force anon subscriptions to be temporary

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -587,8 +587,12 @@
         , (cb2) =>
             @fetchNodeConfig t, cb2
         , (cb2) =>
+            # Anonymous users can only do temporary susbcriptions
+            if @req.actorType is 'anonymous'
+                @req.temporary = true
+
             # Prevent existing persistent subscriptions from being made temporary
-            if @req.temporary
+            else if @req.temporary
                 t.getTemporarySubscription @req.node, @req.actor, (err, subscription, temporary) ->
                     if err
                         return cb2 err


fix cutoff issue

fix cutoff issue

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -248,7 +248,7 @@
                 objSelf.scrollDisabled = false
                 $(window).scroll(objSelf._onScrollFn)
             $('html, body').animate({
-                scrollTop: newArticleOffset.top - $("#top-nav-bar").height()
+                scrollTop: newArticleOffset.top - $("#top-nav-bar").height() - $("#top-nav-bar").offset().top
             }, 500, "swing", () -> setTimeout(doneFn, 0))
             e.preventDefault()

@@ -312,7 +312,7 @@
                objSelf.scrollDisabled = false
                $(window).scroll(objSelf._onScrollFn)
            $('html, body').animate({
-                scrollTop: newArticleOffset.top - $("#top-nav-bar").height()
+                scrollTop: newArticleOffset.top - $("#top-nav-bar").height() - $("#top-nav-bar").offset().top
            }, 500, "swing", () -> setTimeout(doneFn, 0))

     _initializeKeyboardNavigation: ->


Remove empty line

Remove empty line

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -21,7 +21,6 @@
         sourcePath: userKeymapPath
       })

-
   userStyleSheetPath = atom.styles.getUserStyleSheetPath()

   if fs.isFileSync(userStyleSheetPath)


update jetsync lib

update jetsync lib

--- lib/jetsync.coffee
+++ lib/jetsync.coffee
@@ -387,4 +387,3 @@
 root.JetInsert = JetInsert
 root.JetDelta = JetDelta
 root.JetSync = JetSync
-


Replaced msg with res; and added module name to logger

--- src/twitch.coffee
+++ src/twitch.coffee
@@ -28,124 +28,124 @@

 module.exports = (robot) ->
     if not TWITCH_API_KEY?
-        robot.logger.warning "Missing TWITCH_API_KEY in environment. Setting an API key is highly recommended."
+        robot.logger.warning "hubot-twitch: Missing TWITCH_API_KEY in environment. Setting an API key is highly recommended."

     GetTTVData = ->
         robot.brain.data[TWITCH_STORAGE_KEY] or= {}

-    robot.respond /ttv follows/i, (msg) ->
-        user = msg.message.user.name.toLowerCase()
+    robot.respond /ttv follows/i, (res) ->
+        user = res.message.user.name.toLowerCase()
         if twitchUser = GetTTVData()[user]
-            GetTwitchResult msg, "/users/#{twitchUser}/follows/channels", { limit: 10, sortby: "last_broadcast" }, (followsObj) ->
+            GetTwitchResult res, "/users/#{twitchUser}/follows/channels", { limit: 10, sortby: "last_broadcast" }, (followsObj) ->
                 if followsObj._total is 0 or followsObj.status is 404
-                    msg.reply "Your Twitch account is not following anyone or it does not exist."
+                    res.reply "Your Twitch account is not following anyone or it does not exist."
                     return
                 total = 0
                 processing = followsObj.follows.length
                 for followedChannel, index in followsObj.follows
-                    GetTwitchResult msg, "/streams/#{followedChannel.channel.name}", null, (object) ->
+                    GetTwitchResult res, "/streams/#{followedChannel.channel.name}", null, (object) ->
                         --processing
                         if object.status isnt 404 and object.stream
                             ++total
                             channel = object.stream.channel
-                            msg.send "#{channel.display_name} is streaming \"#{channel.status}\" @ #{channel.url}"
+                            res.send "#{channel.display_name} is streaming \"#{channel.status}\" @ #{channel.url}"
                         if processing is 0
                             if total is 0
                                 total = "None"
                             else if total >= 10
                                 total += " or more"
-                            return msg.reply "#{total} of your followed channels are currently streaming."
+                            return res.reply "#{total} of your followed channels are currently streaming."
         else
-            msg.reply "You have not linked your Twitch account yet."
+            res.reply "You have not linked your Twitch account yet."

-    robot.respond /ttv link (.+)/i, (msg) ->
-        user = msg.message.user.name.toLowerCase()
-        twitchUser = msg.match[1]
+    robot.respond /ttv link (.+)/i, (res) ->
+        user = res.message.user.name.toLowerCase()
+        twitchUser = res.match[1]

-        GetTwitchResult msg, "/users/#{twitchUser}", null, (object) ->
+        GetTwitchResult res, "/users/#{twitchUser}", null, (object) ->
             if object.status is 404
-                msg.reply "The user you have entered (\"#{twitchUser}\") does not exist."
+                res.reply "The user you have entered (\"#{twitchUser}\") does not exist."
                 return

             GetTTVData()[user] = twitchUser
             robot.brain.save()
-            msg.reply "Twitch user \"#{twitchUser}\" is now linked to you."
+            res.reply "Twitch user \"#{twitchUser}\" is now linked to you."

-    robot.respond /ttv featured/i, (msg) ->
-        GetTwitchResult msg, '/streams/featured', null, (object) ->
+    robot.respond /ttv featured/i, (res) ->
+        GetTwitchResult res, '/streams/featured', null, (object) ->
             response = ""
             for feature in object.featured
                 channel = feature.stream.channel
                 response += "#{feature.stream.game}: #{channel.display_name} (\"#{channel.status}\") - #{channel.url} [Viewers: #{feature.stream.viewers}]\n"
-            msg.send response
+            res.send response

-    robot.respond /ttv game (.+)/i, (msg) ->
-        category = msg.match[1]
-        GetTwitchResult msg, '/streams', { game: category }, (object) ->
+    robot.respond /ttv game (.+)/i, (res) ->
+        category = res.match[1]
+        GetTwitchResult res, '/streams', { game: category }, (object) ->
             if object._total is 0
-                msg.reply "No live streams were found in \"#{category}\". Try a different category or try again later."
+                res.reply "No live streams were found in \"#{category}\". Try a different category or try again later."
                 return

             response = ""
             for stream in object.streams
                 channel = stream.channel
                 response += "#{channel.display_name} (\"#{channel.status}\"): #{channel.url} [Viewers: #{stream.viewers}]\n"
-            msg.send response
+            res.send response

             if object._total > TWITCH_MAX_RESULTS
-                msg.reply "There are #{object._total - TWITCH_MAX_RESULTS} other \"#{category}\" live streams."
+                res.reply "There are #{object._total - TWITCH_MAX_RESULTS} other \"#{category}\" live streams."

-    robot.respond /ttv search (.+)/i, (msg) ->
-        query = msg.match[1]
-        GetTwitchResult msg, "/search/streams", { q: query }, (object) ->
+    robot.respond /ttv search (.+)/i, (res) ->
+        query = res.match[1]
+        GetTwitchResult res, "/search/streams", { q: query }, (object) ->
             if object._total is 0
-                msg.reply "No live streams were found using search query: \"#{query}\". Try a different query or try again later."
+                res.reply "No live streams were found using search query: \"#{query}\". Try a different query or try again later."
                 return

             response = ""
             for stream in object.streams
                 channel = stream.channel
                 response += "#{channel.display_name} (\"#{channel.status}\"): #{channel.url} [Viewers: #{stream.viewers}]\n"
-            msg.send response
+            res.send response

             if object._total > TWITCH_MAX_RESULTS
-                msg.reply "There are #{object._total - TWITCH_MAX_RESULTS} other live streams matching your search query: \"#{query}\"."
+                res.reply "There are #{object._total - TWITCH_MAX_RESULTS} other live streams matching your search query: \"#{query}\"."

-    robot.respond /ttv stream (.+)/i, (msg) ->
-        GetTwitchResult msg, "/streams/#{msg.match[1]}", null, (object) ->
+    robot.respond /ttv stream (.+)/i, (res) ->
+        GetTwitchResult res, "/streams/#{res.match[1]}", null, (object) ->
             if object.status is 404
-                msg.reply "The stream you have entered (\"#{msg.match[1]}\") does not exist."
+                res.reply "The stream you have entered (\"#{res.match[1]}\") does not exist."
                 return

             if not object.stream
-                msg.reply "The stream you have entered (\"#{msg.match[1]}\") is currently offline. Try again later."
+                res.reply "The stream you have entered (\"#{res.match[1]}\") is currently offline. Try again later."
                 return

             channel = object.stream.channel
             response = "#{channel.display_name} is streaming #{channel.game} @ #{channel.url}\n"
             response += "Stream status: #{channel.status}\n"
             response += "Viewers: #{object.stream.viewers}"
-            msg.send response
+            res.send response

-    robot.respond /ttv top/i, (msg) ->
+    robot.respond /ttv top/i, (res) ->
         createGameURL = (game) ->
             "https://www.twitch.tv/directory/game/#{encodeURIComponent(game)}"

-        GetTwitchResult msg, "/games/top", null, (object) ->
+        GetTwitchResult res, "/games/top", null, (object) ->
             response = ""
             for gameObj, i in object.top
                 response += "#{i + 1}. #{gameObj.game.name} | Viewers: #{gameObj.viewers} | Channels: #{gameObj.channels} | #{createGameURL(gameObj.game.name)}\n"
-            msg.send response
+            res.send response

-GetTwitchResult = (msg, api, params = {}, handler) ->
+GetTwitchResult = (res, api, params = {}, handler) ->
     params.client_id = TWITCH_API_KEY
     params.limit or= TWITCH_MAX_RESULTS
-    msg.http("https://api.twitch.tv/kraken#{api}")
+    res.http("https://api.twitch.tv/kraken#{api}")
         .query(params)
-        .get() (err, res, body) ->
-            if err or res.statusCode isnt 200
-                err = "503 Service Unavailable" if res.statusCode is 503
-                msg.reply "An error occurred while attempting to process your request."
-                return msg.robot.logger.error err
+        .get() (err, httpRes, body) ->
+            if err or httpRes.statusCode isnt 200
+                err = "503 Service Unavailable" if httpRes.statusCode is 503
+                res.reply "An error occurred while attempting to process your request."
+                return res.robot.logger.error "hubot-twitch: #{err}"

             handler JSON.parse(body)


Fix moving to end of last word in file with 'w'

--- lib/motions.coffee
+++ lib/motions.coffee
@@ -125,7 +125,14 @@
 class MoveToNextWord extends Motion
   execute: (count=1) ->
     _.times count, =>
-      @editor.moveCursorToBeginningOfNextWord()
+      cursor = @editor.getCursor()
+      current = cursor.getBufferPosition()
+      next = cursor.getBeginningOfNextWordBufferPosition()
+
+      if current != next
+        @editor.moveCursorToBeginningOfNextWord()
+      else
+        @editor.moveCursorToEndOfWord()

   # Options
   #  excludeWhitespace - if true, whitespace shouldn't be selected
@@ -136,7 +143,7 @@
       current = cursor.getBufferPosition()
       next = cursor.getBeginningOfNextWordBufferPosition()

-      if current.row != next.row or excludeWhitespace
+      if current.row != next.row or excludeWhitespace or current == next
         @editor.selectToEndOfWord()
       else
         @editor.selectToBeginningOfNextWord()


Move the cursor directly

Move the cursor directly

--- lib/motions.coffee
+++ lib/motions.coffee
@@ -124,15 +124,16 @@

 class MoveToNextWord extends Motion
   execute: (count=1) ->
-    _.times count, =>
-      cursor = @editor.getCursor()
+    cursor = @editor.getCursor()
+
+    _.times count, =>
       current = cursor.getBufferPosition()
       next = cursor.getBeginningOfNextWordBufferPosition()

       if current != next
-        @editor.moveCursorToBeginningOfNextWord()
+        cursor.moveToBeginningOfNextWord()
       else
-        @editor.moveCursorToEndOfWord()
+        cursor.moveToEndOfWord()

   # Options
   #  excludeWhitespace - if true, whitespace shouldn't be selected


Allow multiple jobs

Allow multiple jobs

--- src/request-queue.coffee
+++ src/request-queue.coffee
@@ -6,9 +6,9 @@
   @HIGH: 2
   @VERY_HIGH: 3

-  constructor: ->
+  constructor: (@workerCount=1) ->
     @jobs = []
-    @currentJob = null
+    @runningJobs = []
     @_emitter = new Emitter

   enqueue: (request, priority) ->
@@ -25,14 +25,16 @@
   off: (args...) -> @_emitter.off args...

   _checkQueue: ->
-    return if @currentJob
+    return if @runningJobs.length >= @workerCount

-    @currentJob = @_dequeue()
-    @_emitter.emit "start", @currentJob
+    while @jobs.length > 0 and @runningJobs.length < @workerCount
+      nextJob = @_dequeue()
+      @_emitter.emit "start", nextJob
+      @runningJobs.push nextJob

-    request = @currentJob.request
-    request.on "complete", @_requestComplete.bind(this)
-    request.send()
+      request = nextJob.request
+      request.on "complete", @_jobComplete.bind(this, nextJob)
+      request.send()

   _dequeue: ->
     return null if @jobs.length is 0
@@ -53,8 +55,10 @@

     false

-  _requestComplete: ->
-    @_emitter.emit "finish", @currentJob
+  _jobComplete: (job) ->
+    @_emitter.emit "finish", job

-    @currentJob = null
+    index = @runningJobs.indexOf(job)
+    @runningJobs.splice(index, 1)
+
     @_checkQueue()


:art: Remove unecessary function

:art: Remove unecessary function

--- src/request-queue.coffee
+++ src/request-queue.coffee
@@ -27,7 +27,7 @@
   _checkQueue: ->
     return if @runningJobs.length >= @workerCount

-    while @_jobCanBeQueue()
+    while @jobs.length > 0 and @runningJobs.length < @workerCount
       job = @_dequeue()
       @_startJob job

@@ -62,6 +62,3 @@
     @runningJobs.splice(index, 1)

     @_checkQueue()
-
-  _jobCanBeQueue: ->
-    @jobs.length > 0 and @runningJobs.length < @workerCount


[wip] 開いたページ内でcoffeeで書いたスクリプトを実行できるようになった

refs #2

--- src/w-cart.coffee
+++ src/w-cart.coffee
@@ -19,9 +19,15 @@

 casper.each urls, (casper, url, i) ->
   @thenOpen url, ->
-    @echo @getTitle()
+    # @echo @getTitle()

-    capture @, url, 'original.png'
+    console.log 'aaa'
+
+    # capture @, url, 'original.png'
+    result = @evaluate replaceAllChars
+
+    console.log result
+    console.log 'bbb'

 casper.run()

@@ -39,3 +45,9 @@
              #{filename}"

   casper.capture filepath.replace(/\s+/g, '')
+
+###
+  開いたページのすべての文字列を〼に置き換える関数
+###
+replaceAllChars = ->
+  return document.title


Lowercasing Robot name also

Lowercasing Robot name also

Better matching.

https://github.com/hipchat/hubot-hipchat/pull/88

--- src/scripts/roles.coffee
+++ src/scripts/roles.coffee
@@ -54,7 +54,7 @@
             msg.send "I know"
           else
             user.roles.push(newRole)
-            if name.toLowerCase() is robot.name
+            if name.toLowerCase() is robot.name.toLowerCase()
               msg.send "Ok, I am #{newRole}."
             else
               msg.send "Ok, #{name} is #{newRole}."


Removed pane

Removed pane

--- lib/pg-hoff.coffee
+++ lib/pg-hoff.coffee
@@ -153,16 +153,12 @@
         resultsPane = new GulpPaneView()
         @hoffPanes.push resultsPane

-        resultsPane2 = new GulpPaneView()
-        @hoffPanes.push resultsPane2
-
         config =
           name: 'YOLOPANE'
           id: resultsPane.getId()
           active: true

         @bottomDock.addPane resultsPane, 'Results', isInitial
-        @bottomDock.addPane resultsPane2, 'Results2', isInitial

         @bottomDock.onDidToggle =>


add extensions:loaded event

add extensions:loaded event

--- src/window.coffee
+++ src/window.coffee
@@ -57,6 +57,8 @@
         console.warn "window: Extension #{extension.constructor.name} failed to startup."
         console.warn error

+    Event.trigger 'extensions:loaded'
+
   loadKeyBindings: ->
     KeyBinder.load "#{@appRoot}/static/key-bindings.coffee"
     if fs.isFile "~/.atomicity/key-bindings.coffee"


Attempt to fix fix reports

Attempt to fix fix reports

--- lib/reports.coffee
+++ lib/reports.coffee
@@ -1,14 +1,29 @@
 Server = require '../models/server'
 Game = require '../models/game'
 Player = require '../models/player'
+async = require 'async'

-pushRank = (rk, rankings) ->
-  rankings.push
-    time: rk.time
-    player: rk.player
-    kills: rk.kills
-    class: rk.class
-    rank: rk.rank
+makeRank = (rk) ->
+  time: rk.time
+  player: rk.player
+  kills: rk.kills
+  class: rk.class
+  rank: rk.rank
+
+verifyPlayer = (rk, cb) ->
+  Player.findOne().where('name', rk.player).exec (err, player) =>
+    return cb err, null
+
+    unless player
+      player = new Player
+        name: rk.player
+      player.save =>
+        rk.player = player
+        cb null, makeRank(rk)
+    else
+      rk.player = player
+      cb null, makeRank(rk)
+

 exports.parse = (report, cb) ->
   ###
@@ -22,31 +37,15 @@
       if err or not server
         return cb new Error('Unknown server'), null

-      game = new Game
-        server: server
+      async.map report.rankings, verifyPlayer, (err, rankings) ->
+        game = new Game
+          server: server

-        type: report.type
-        date: new Date(report.date)
-        rankings: (=>
-          rankings = []
-          for rk in report.rankings
-            Player.findOne().where('name', rk.player).exec (err, player) =>
-              return cb err, null
+          type: report.type
+          date: new Date(report.date)
+          rankings: rankings

-              unless player
-                player = new Player
-                  name: rk.player
-                player.save =>
-                  rk.player = player
-                  pushRank rk, rankings
-              else
-                rk.player = player
-                pushRank rk, rankings
-
-          return rankings
-        )()
-
-      cb null, game
+        cb null, game

   catch e
     cb e, null


添加unregisterAPNS、unregisterGCM接口；修改registerGCM、registerAPNS、getToken函数，如果token已存在，则先清理旧event，再增加。

--- lib/steedos.coffee
+++ lib/steedos.coffee
@@ -102,6 +102,14 @@
                 fields.clientBuildNumber = req.param("clientBuildNumber")

             createSubscriber fields, (subscriber, created) ->
+
+                if created == false
+                    subscriber.getSubscriptions (subs) ->
+                        for sub in subs
+                            subscriber.removeSubscription sub.event, (deleted) ->
+                                if not deleted
+                                    logger.error "No subscriber #{req.subscriber.id}"
+
                 subscriber.get (info) ->
                     info.id = subscriber.id
                     if req.body.pushTopics?
@@ -137,6 +145,14 @@
             fields.proto = "apns" + "|" + fields.appId

             createSubscriber fields, (subscriber, created) ->
+
+                if created == false
+                    subscriber.getSubscriptions (subs) ->
+                        for sub in subs
+                            subscriber.removeSubscription sub.event, (deleted) ->
+                                if not deleted
+                                    logger.error "No subscriber #{req.subscriber.id}"
+
                 subscriber.get (info) ->
                     info.id = subscriber.id
                     if req.body.pushTopics?
@@ -158,13 +174,20 @@

     # will be removed soon, use getToken instead
     app.post '/registerGCM', authorize('register'), (req, res) ->
-
         logger.verbose "registerGCM: " + JSON.stringify(req.body)
         try
             fields = {}
             fields.proto = "gcm"
             fields.token = req.body.pushToken
             createSubscriber fields, (subscriber, created) ->
+
+                if created == false
+                    subscriber.getSubscriptions (subs) ->
+                        for sub in subs
+                            subscriber.removeSubscription sub.event, (deleted) ->
+                                if not deleted
+                                    logger.error "No subscriber #{req.subscriber.id}"
+
                 subscriber.get (info) ->
                     info.id = subscriber.id
                     if req.body.pushTopics?
@@ -232,6 +255,57 @@
             res.json error: error.message, 200

+    app.post '/unregisterAPNS', authorize('register'), (req, res) ->
+
+        logger.verbose "unregisterAPNS: " + JSON.stringify(req.body)
+
+        try
+            if req.body.pushToken?
+                req.subscriber = getSubscriberFromId(req.body.pushToken)
+            else
+                throw new Error("pushToken not found")
+
+            if req.body.pushTopics?
+                events = {};
+                for topic in req.body.pushTopics
+                    eventName = topic
+                    if req.param("steedosId")?
+                        eventName = eventName + "|" + req.param("steedosId").replace(/[@]/g, "_")
+                    else if req.body.steedosId?
+                        eventName = eventName + "|" + req.body.steedosId.replace(/[@]/g, "_")
+                    events[eventName] = {}
+                req.subscriber.removeSubscriptions events, (r) ->
+                    res.json {}, 200
+
+        catch error
+            logger.error "unregisterAPNS failed: #{error.message}"
+            res.json error: error.message, 200
+
+    app.post '/unregisterGCM', authorize('register'), (req, res) ->
+
+        logger.verbose "unregisterGCM: " + JSON.stringify(req.body)
+
+        try
+            if req.body.pushToken?
+                req.subscriber = getSubscriberFromId(req.body.pushToken)
+            else
+                throw new Error("pushToken not found")
+
+            if req.body.pushTopics?
+                events = {};
+                for topic in req.body.pushTopics
+                    eventName = topic
+                    if req.param("steedosId")?
+                        eventName = eventName + "|" + req.param("steedosId").replace(/[@]/g, "_")
+                    else if req.body.steedosId?
+                        eventName = eventName + "|" + req.body.steedosId.replace(/[@]/g, "_")
+                    events[eventName] = {}
+                req.subscriber.removeSubscriptions events, (r) ->
+                    res.json {}, 200
+
+        catch error
+            logger.error "unregisterGCM failed: #{error.message}"
+            res.json error: error.message, 200

     app.post '/message', authorize('publish'), (req, res) ->


Implement code-block style metadata. Refs #154

--- src/plugins/markdown.coffee
+++ src/plugins/markdown.coffee
@@ -89,6 +89,12 @@
       if result?.length is 3
         metadata = result[1]
         markdown = result[2]
+    else if content[0...12] is '```metadata\n'
+      # "Winter Matter"
+      end = content.indexOf '\n```\n'
+      if end isnt -1
+        metadata = content.substring 12, end
+        markdown = content.substring end + 5

     async.parallel
       metadata: (callback) ->


remove logging

remove logging

--- src/ui/models/viewstate.coffee
+++ src/ui/models/viewstate.coffee
@@ -8,8 +8,6 @@
     STATE_STARTUP: 'startup'
     STATE_NORMAL: 'normal'
     STATE_ADD_CONVERSATION: 'add_conversation'
-
-console.log tryparse(localStorage.muteSoundNotification), localStorage

 module.exports = exp = {
     state: null


formatting the state file

formatting the state file

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -57,7 +57,7 @@

 saveState = ->
   plugins.call "saveState", state
-  fs.writeFile process.env.npm_package_config_state_file, JSON.stringify(state), "utf8"
+  fs.writeFile process.env.npm_package_config_state_file, JSON.stringify(state, null, 4), "utf8"

 restoreState = ->
   try loaded_state = JSON.parse fs.readFileSync process.env.npm_package_config_state_file, "utf8"


date last worked

date last worked

--- src/components/scores/concept-coach-cell.cjsx
+++ src/components/scores/concept-coach-cell.cjsx
@@ -1,5 +1,6 @@
 React  = require 'react'
 Router = require 'react-router'
+BS = require 'react-bootstrap'

 Time = require '../time'
 CellStatusMixin = require './cell-status-mixin'
@@ -10,7 +11,13 @@
   mixins: [CellStatusMixin] # prop validation

   render: ->
-    pieValue = Math.round((@props.task.correct_exercise_count / @props.task.exercise_count) * 100)
+    pieValue =
+      Math.round((@props.task.correct_exercise_count / @props.task.exercise_count) * 100)
+    tooltip =
+      <BS.Tooltip>
+
Date Last Worked: <Time format='MMM. D' date={@props.task.last_worked_at} />

+      </BS.Tooltip>
+

       <Router.Link className="score" to='viewTaskStep'
         data-assignment-type="#{@props.task.type}"
@@ -22,9 +29,15 @@
             "#{pieValue}%"
         }
       </Router.Link>
-

-        <PieProgress size={25} value={pieValue} />
-

+      <BS.OverlayTrigger
+        placement='top'
+        delayShow={1000}
+        delayHide={0}
+        overlay={tooltip}>
+

+          <PieProgress size={25} value={pieValue} />
+

+      </BS.OverlayTrigger>


fix sql query checking last access time

fix sql query checking last access time

--- services/clsi/app/coffee/ProjectPersistenceManager.coffee
+++ services/clsi/app/coffee/ProjectPersistenceManager.coffee
@@ -72,7 +72,7 @@
 		job = (cb)->
 			keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)
 			q = {}
-			q[db.op.gt] = keepProjectsFrom
+			q[db.op.lt] = keepProjectsFrom
 			db.Project.findAll(where:{lastAccessed:q})
 				.then((projects) ->
 					cb null, projects.map((project) -> project.project_id)


Added comparator to sort lists in checklists view.

--- collections/checklists.coffee
+++ collections/checklists.coffee
@@ -11,3 +11,6 @@
     forTemplate: ->
         checklists: @toJSON()
         sd: sd
+
+    comparator: (a, b) ->
+        b.get('createdAt') - a.get('createdAt')


UPDATED - RectangleElement uses the new rectanglePath function

--- source/Art/Engine/Elements/Shapes/RectangleElement.coffee
+++ source/Art/Engine/Elements/Shapes/RectangleElement.coffee
@@ -4,7 +4,7 @@
 ShadowableElement = require '../ShadowableElement'
 {Paths} = require 'art-canvas'
 {pureMerge, floatEq, AtomElement, createWithPostCreate, isPlainObject, isNumber} = Foundation
-{curriedRoundedRectangle, roundedRectangle} = Paths
+{rectanglePath} = Paths

 module.exports = createWithPostCreate class RectangleElement extends ShadowableElement

@@ -34,7 +34,7 @@
     if floatEq @_radius, 0
       super
     else
-      lastClippingInfo = target.openClipping roundedRectangle, elementToTargetMatrix, target.pixelSnapRectangle(elementToTargetMatrix, @getPaddedArea()), @_radius
+      lastClippingInfo = target.openClipping rectanglePath, elementToTargetMatrix, target.pixelSnapRectangle(elementToTargetMatrix, @getPaddedArea()), @_radius
       @_drawChildren target, elementToTargetMatrix
       target.closeClipping lastClippingInfo


Improve french strings

Improve french strings

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -1,7 +1,7 @@
 module.exports =
     "default calendar name"             : "mon agenda"
     "Add"                               : "Ajouter"
-    "event"                             : "événement"
+    "event"                             : "évènement"
     "create event"                      : "Création d'un évènement"
     "edit event"                        : "Modification d'un évènement"
     "edit"                              : "Enregistrer"
@@ -13,8 +13,8 @@
     "delete"                            : "Supprimer"
     "rename"                            : "Renommer"
     "export"                            : "Exporter"
-    "remove"                            : "Supprimer l'événement"
-    "duplicate"                         : "Dupliquer l'événement"
+    "remove"                            : "Supprimer l'évènement"
+    "duplicate"                         : "Dupliquer l'évènement"
     "Place"                             : "Lieu"
     'all day'                           : 'journée entière'
     'All day'                           : 'Journée entière'
@@ -59,12 +59,12 @@
     "Week"                              : "Semaine"
     "Display"                           : "Notification"
     "DISPLAY"                           : "Notification"
-    "EMAIL"                             : "E-mail"
-    "BOTH"                              : "E-mail & notification"
+    "EMAIL"                             : "Email"
+    "BOTH"                              : "Email & notification"
     "display previous events"           : "Afficher les évènements précédents"
     "display next events"           : "Afficher les évènements suivants"
     "are you sure"                      : "Êtes-vous sûr(e) ?"
-    "confirm delete calendar"           : "Vous êtes sur le point de supprimer tous les événements associés à %{calendarName}. Êtes-vous sûr(e) ?"
+    "confirm delete calendar"           : "Vous êtes sur le point de supprimer tous les évènements associés à %{calendarName}. Êtes-vous sûr(e) ?"
     "advanced"                          : "Détails"
     "enter email"                       : "Entrer l'adresse email"
     "ON"                                : "activée"
@@ -73,7 +73,7 @@
     "add calendar"                      : "Ajouter un calendrier"
     "new calendar"                      : "Nouveau calendrier"

-# RRULE related
+# RULE related
     "recurrence"                        : "Récurrence"
     "recurrence rule"                   : "Règle de récurrence"
     "make reccurent"                    : "Rendre récurrent"
@@ -95,11 +95,11 @@
     "days"                              : "jours"
     "day"                               : "jour"
     "weeks"                             : "semaines"
-    "week"                              : "semaines"
+    "week"                              : "semaine"
     "months"                            : "mois"
     "month"                             : "mois"
     "years"                             : "ans"
-    "year"                              : "ans"
+    "year"                              : "an"
     "until"                             : "jusqu'au"
     "for"                               : "pour"
     "on"                                : "le"
@@ -140,7 +140,7 @@
     'no'                                : 'Non'

 # ERRORS
-    "no summary"                        : "Le titre est obligatoire"
+    "no summary"                        : "Le titre est obligatoire."
     "start after end"                   : "La fin est après le début."
     "invalid start date"                : "Le début est invalide."
     "invalid end date"                  : "La fin est invalide."
@@ -153,7 +153,7 @@
     "synchronization"                   : "Synchronisation"
     "mobile sync"                       : "Synchro Mobile (CalDAV)"
     "import an ical file"               : "Pour importer un fichier iCal dans votre agenda, commencez par cliquer sur ce bouton pour le précharger :"
-    "link imported events with calendar": "Lier les événements à importer avec le calendrier suivant:"
+    "link imported events with calendar": "Lier les évènements à importer avec le calendrier suivant :"
     "download a copy of your calendar"  : "Sélectionner un agenda puis cliquer sur le bouton exporter pour télécharger une copie de l'agenda comme un fichier iCal :"
     "icalendar export"                  : "Exporter ICalendar"
     "icalendar import"                  : "Importer ICalendar"
@@ -166,8 +166,8 @@
     "sync help link"                    : "guide pas à pas !"
     "install the sync module"           : "Installer le module Sync depuis l'applithèque."
     "connect to it and follow"          : "Vous connecter et suivre les instructions relatives à CalDAV."
-    "some event fail to save"           : "La sauvegarde d'un événement a échoué."
-    "imported events": "Nombre d'événements importés"
+    "some event fail to save"           : "La sauvegarde d'un évènement a échoué."
+    "imported events": "Nombre d'évènements importés"
     "import finished": "Votre importation est terminée !"
     "import error occured for": "Une erreur est survenue pour un de ces éléments "
     "export your calendar": "Exporter votre agenda"
@@ -202,9 +202,9 @@
 # Emails
     'email date format': 'DD/MM/YYYY [à] HH[h]mm'
     'email date format allday': 'DD/MM/YYYY [toute la journée]'
-    'email invitation title': "Invitation à l'événement \"%{description}\""
+    'email invitation title': "Invitation à l'évènement \"%{description}\""
     'email invitation content': """
-        Bonjour, je souhaiterais vous inviter à l'événement suivant :
+        Bonjour, je souhaiterais vous inviter à l'évènement suivant :
         %{description} %{place}
         Le %{date}
         Serez-vous présent ?
@@ -215,9 +215,9 @@
         Non
         %{url}?status=DECLINED&key=%{key}
     """
-    'email update title': "L'événement \"%{description}\" a changé"
+    'email update title': "L'évènement \"%{description}\" a changé"
     'email update content': """
-        Un événement auquel vous participez a changé :
+        Un évènement auquel vous participez a changé :
         %{description} %{place}
         Le %{date}

@@ -228,9 +228,9 @@
         %{url}?status=DECLINED&key=%{key}

     """
-    'email delete title': 'Cet événement a été annulé : %{description}'
+    'email delete title': 'Cet évènement a été annulé : %{description}'
     'email delete content': """
-        Cet événement a été annulé :
+        Cet évènement a été annulé :
         %{description} %{place}
         Le %{date}
     """


added log line

added log line

--- app/coffee/DispatchManager.coffee
+++ app/coffee/DispatchManager.coffee
@@ -16,6 +16,7 @@
 			_waitForUpdateThenDispatchWorker: (callback = (error) ->) ->
 				timer = new Metrics.Timer "worker.waiting"
 				worker.client.blpop "pending-updates-list", 0, (error, result) ->
+					logger.log("getting pending-updates-list", error, result)
 					timer.done()
 					return callback(error) if error?
 					return callback() if !result?


remove (non-exitent) supplemental tutorial on classification 10

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -72,7 +72,7 @@
     ifFaved: false

     # classification counts at which to display supplementary tutorial
-    @whenToDisplayTips = [1, 4, 10]
+    @whenToDisplayTips = [1, 4]

     User.on 'change', @onUserChange
     Subject.on 'fetch', @onSubjectFetch