grab properly the id from the url

grab properly the id from the url

--- client/app/views/gallery.coffee
+++ client/app/views/gallery.coffee
@@ -82,7 +82,8 @@
     getIdPhoto: () =>
         url = $('#pbOverlay .wrapper img.zoomable').attr 'src'
         console.log 'url: ' + url
-        id = url.split('/')[4]
+        parts = url.split('/')
+        id = parts[parts.length - 1]
         id = id.split('.')[0]
         return id


test(view): update for ng-1.2 compat

test(view): update for ng-1.2 compat

--- test/spec/services/view.coffee
+++ test/spec/services/view.coffee
@@ -49,7 +49,7 @@
       state.transitionTo 'home'
       expect(viewService.getDirection
         to: 'second'
-      ).toBe 'none'
+      ).toBe ''

     it 'should detect reverse direction using state objects', ->
       expect(viewService.getDirection
@@ -90,13 +90,13 @@
     it 'should detect none direction using "equal" URLs', ->
       expect(
         viewService.getDirection '/home/third', '/home/second'
-      ).toBe 'none'
+      ).toBe ''

     it 'should detect none direction from "^" url', ->
       state.transitionTo 'home'
       expect(viewService.getDirection
         to: '/second'
-      ).toBe 'none'
+      ).toBe ''

     it 'should not care about trailing slashes', ->
       expect(viewService.getDirection '/home/', '/home/second').toBe 'normal'


Fix review update

Fix review update

--- client/components/Review.coffee
+++ client/components/Review.coffee
@@ -128,9 +128,10 @@
         @setState
             results: @state.results[...-1]

-    componentDidUpdate: () ->
-        @state =
-            results: (r for r in props.data.ok when r.userList != "unknown")
+    componentDidUpdate: (prevProps) ->
+        if @props.data[0]?._id != prevProps.data[0]?._id
+            @setState
+                results: (r for r in @props.data.ok when r.userList != "unknown")

     render: () ->
         <div>


webapp/sagews2html: process *all* mesg types, not just one

--- src/smc-webapp/printing.coffee
+++ src/smc-webapp/printing.coffee
@@ -323,26 +323,27 @@
         return @_html_tmpl

     html_process_output_mesg: (mesg, mark) ->
-        out = null
+        # Note: each mesg could contain more than one output type
+        out = ''
         # console.log 'html_process_output_mesg', mesg, mark
         if mesg.stdout?
             # assertion: for stdout, `mark` might be undefined
-            out = "
#{mesg.stdout}
"
-        else if mesg.stderr?
-            out = "
#{mesg.stderr}
"
-        else if mesg.html?
+            out += "
#{mesg.stdout}
"
+        if mesg.stderr?
+            out += "
#{mesg.stderr}
"
+        if mesg.html?
             $html = $("
#{mesg.html}
")
             @editor.syncdoc.process_html_output($html)
-            out = "
#{$html.html()}
"
-        else if mesg.md?
+            out += "
#{$html.html()}
"
+        if mesg.md?
             x = markdown.markdown_to_html(mesg.md)
             $out = $("
")
             $out.html_noscript(x.s) # also, don't process mathjax!
             @editor.syncdoc.process_html_output($out)
-            out = "
#{$out.html()}
"
-        else if mesg.interact?
-            out = "
#{mark.widgetNode.innerHTML}
"
-        else if mesg.file?
+            out += "
#{$out.html()}
"
+        if mesg.interact?
+            out += "
#{mark.widgetNode.innerHTML}
"
+        if mesg.file?
             if mesg.file.show ? true
                 ext = misc.filename_extension(mesg.file.filename).toLowerCase()
                 if ext == 'sage3d'
@@ -357,7 +358,6 @@
                         out ?= ''
                         out += "

"
                 else if ext == 'webm'
-                    out ?= ''
                     # 'raw' url. later, embed_videos will be replace by the data-uri if there is no error
                     out += "<video src='#{mesg.file.url}' class='sagews-output-video' controls></video>"
                 else
@@ -367,22 +367,23 @@
                         # console.log "output.file", mark, mesg
                         $images = $(mark.widgetNode)
                         for el in $images.find('.sagews-output-image')
-                            out ?= ''
                             # innerHTML should just be the  element
                             out += el.innerHTML
-                        out = "
#{out}
"
-        else if mesg.code?  # what's that actually?
+                        out += "
#{out}
"
+        if mesg.code?  # what's that actually?
             code = mesg.code.source
-            out = "
#{code}
"
-        else if mesg.javascript?
+            out += "
#{code}
"
+        if mesg.javascript?
             # mesg.javascript.coffeescript is true iff coffeescript
             $output = $(mark.replacedWith)
             $output.find('.sagews-output-container').remove() # TODO what's that?
-            out = "
#{$output.html()}
"
-        else if mesg.done?
+            out += "
#{$output.html()}
"
+        if mesg.done?
             # ignored
-        else
-            console.warn "ignored mesg", mesg
+            out += ' '
+
+        if out == ''
+            console.warn "sagews2html: encounted unknown mesg", mesg
         return @html_post_process(out)

     html_post_process: (html) ->


fix accept filters internal name reference

--- lib/index.coffee
+++ lib/index.coffee
@@ -28,7 +28,7 @@

     # store both accept filters provided (when not provided, they become undefined)
     for accept in ['acceptString', 'acceptPath']
-      @[accept] = options[accept]
+      @['_' + accept] = options[accept]

     # always recurse unless options specify *not* to
     if options.recurse isnt false then @recurse = true


improve output message

improve output message

--- lib/index.coffee
+++ lib/index.coffee
@@ -47,10 +47,8 @@

         if localError then next true else JSON.save filename, newPkg, next
   , ->
-
-    if globalError
-      cb('Someting is wrong. Resolve red messages to continue.')
-    else
-      plugin.logger
-      plugin.logger[globalMessageType] globalMessage
-      cb()
+    return cb 'Someting is wrong. Resolve red messages to continue.' if  globalError
+    globalMessage = globalMessage.replace 'file', 'files' if bumped.config.rc.files.length > 1
+    plugin.logger
+    plugin.logger[globalMessageType] globalMessage
+    cb()


接続先をいい感じに

接続先をいい感じに

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -4,7 +4,7 @@

 SocketService = require('./SocketService')

-socketService = new SocketService('localhost:8001/websocket')
+socketService = new SocketService('https://erl-editor.herokuapp.com//websocket')

 global.React = require('react')
 md2react = require('md2react')


Fix an issue with invalid section id in the URL.

--- coffee/ruban.coffee
+++ coffee/ruban.coffee
@@ -3,6 +3,7 @@
     @initOptions()
     @$sections = $('section').wrapAll('

')
     @$ruban    = $('.ruban')
+    @$current  = @$sections.first()

     @toc()
     @checkHash()


Add one more describe level.

Add one more describe level.

--- src/spec/validator.spec.coffee
+++ src/spec/validator.spec.coffee
@@ -2,97 +2,102 @@
 Validator = require('../main').Validator
 CONS =  require '../lib/constants'

-describe '#Validator', ->
+describe 'Validator', ->
   beforeEach ->
     @validator = new Validator()

-  it 'should initialize', ->
-    expect(@validator).toBeDefined()
+  describe '@constructor', ->
+    it 'should initialize', ->
+      expect(@validator).toBeDefined()

-describe '#parse', ->
-  beforeEach ->
-    @validator = new Validator()
+  describe '#parse', ->
+    beforeEach ->
+      @validator = new Validator()

-  it 'should parse string', (done) ->
-    @validator.parse 'foo', (data, count) ->
-      expect(count).toBe 1
-      done()
+    it 'should parse string', (done) ->
+      @validator.parse 'foo', (data, count) ->
+        expect(count).toBe 1
+        done()

-describe '#valHeader', ->
-  beforeEach ->
-    @validator = new Validator()
+  describe '#valHeader', ->
+    beforeEach ->
+      @validator = new Validator()

-  it 'should return error for each missing header', (done) ->
-    @validator.parse 'foo,productType\n1,2', (data, count) =>
-      @validator.valHeader data[0]
-      expect(@validator.errors.length).toBe 2
-      expect(@validator.errors[0]).toBe "Can't find necessary base header 'name'!"
-      expect(@validator.errors[1]).toBe "Can't find necessary base header 'variantId'!"
-      done()
+    it 'should return error for each missing header', (done) ->
+      @validator.parse 'foo,productType\n1,2', (data, count) =>
+        @validator.valHeader data[0]
+        expect(@validator.errors.length).toBe 2
+        expect(@validator.errors[0]).toBe "Can't find necessary base header 'name'!"
+        expect(@validator.errors[1]).toBe "Can't find necessary base header 'variantId'!"
+        done()

-  it 'should return error on duplicate header', (done) ->
-    @validator.parse 'productType,name,variantId,name\n1,2,3,4', (data, count) =>
-      @validator.valHeader data[0]
-      expect(@validator.errors.length).toBe 1
-      expect(@validator.errors[0]).toBe "There are duplicate header entries!"
-      done()
+    it 'should return error on duplicate header', (done) ->
+      @validator.parse 'productType,name,variantId,name\n1,2,3,4', (data, count) =>
+        @validator.valHeader data[0]
+        expect(@validator.errors.length).toBe 1
+        expect(@validator.errors[0]).toBe "There are duplicate header entries!"
+        done()

-describe '#isVariant', ->
-  beforeEach ->
-    @validator = new Validator()
-    @validator.header2index CONS.BASE_HEADERS
+  describe '#isVariant', ->
+    beforeEach ->
+      @validator = new Validator()
+      @validator.header2index CONS.BASE_HEADERS

-  it 'should be true for a variant', ->
-    expect(@validator.isVariant ['', '', 2]).toBe true
+    it 'should be true for a variant', ->
+      expect(@validator.isVariant ['', '', 2]).toBe true

-  it 'should be false for a product', ->
-    expect(@validator.isVariant ['myProduct', 1]).toBe false
+    it 'should be false for a product', ->
+      expect(@validator.isVariant ['myProduct', 1]).toBe false

-describe '#buildProducts', ->
-  beforeEach ->
-    @validator = new Validator()
-    @validator.header2index CONS.BASE_HEADERS
+  describe '#buildProducts', ->
+    beforeEach ->
+      @validator = new Validator()
+      @validator.header2index CONS.BASE_HEADERS

-  it 'should build 2 products their variants', (done) ->
-    csv = "productType,name,variantId\n
+    it 'should build 2 products their variants', (done) ->
+      csv = "
+productType,name,variantId\n
 foo,n1,1\n
 ,,2\n
 ,,3\n
 bar,n2,1\n
 ,,2"

-    @validator.parse csv, (data, count) =>
-      @validator.buildProducts _.rest data
-      expect(@validator.errors.length).toBe 0
-      expect(@validator.products.length).toBe 2
-      expect(@validator.products[0].master).toEqual ['foo', 'n1', '1']
-      expect(@validator.products[0].variants.length).toBe 2
-      expect(@validator.products[0].startRow).toBe 1
-      expect(@validator.products[1].master).toEqual ['bar', 'n2', '1']
-      expect(@validator.products[1].variants.length).toBe 1
-      expect(@validator.products[1].startRow).toBe 4
-      done()
+      @validator.parse csv, (data, count) =>
+        @validator.buildProducts _.rest data
+        expect(@validator.errors.length).toBe 0
+        expect(@validator.products.length).toBe 2
+        expect(@validator.products[0].master).toEqual ['foo', 'n1', '1']
+        expect(@validator.products[0].variants.length).toBe 2
+        expect(@validator.products[0].startRow).toBe 1
+        expect(@validator.products[1].master).toEqual ['bar', 'n2', '1']
+        expect(@validator.products[1].variants.length).toBe 1
+        expect(@validator.products[1].startRow).toBe 4
+        done()

-  it 'should return error if first row in not a product', (done) ->
-    csv = "productType,name,variantId\n
+    it 'should return error if first row in not a product', (done) ->
+      csv = "
+productType,name,variantId\n
 ,,1\n
 ,,2"

-    @validator.parse csv, (data, count) =>
-      @validator.buildProducts _.rest data
-      expect(@validator.errors.length).toBe 2
-      expect(@validator.errors[0]).toBe '[row 1] We need a product before starting with a variant!'
-      expect(@validator.errors[1]).toBe '[row 2] We need a product before starting with a variant!'
-      done()
+      @validator.parse csv, (data, count) =>
+        @validator.buildProducts _.rest data
+        expect(@validator.errors.length).toBe 2
+        expect(@validator.errors[0]).toBe '[row 1] We need a product before starting with a variant!'
+        expect(@validator.errors[1]).toBe '[row 2] We need a product before starting with a variant!'
+        done()

-describe '#validate', ->
-  beforeEach ->
-    @validator = new Validator()
+  describe '#validate', ->
+    beforeEach ->
+      @validator = new Validator()

-  it 'should return no error', (done) ->
-    csv = "productType,name,variantId\n
+    it 'should return no error', (done) ->
+      csv = "
+productType,name,variantId\n
 foo,bar,bla"
-    @validator.parse csv, (data, count) =>
-      @validator.validate data
-      expect(@validator.errors).toEqual []
-      done()
+
+      @validator.parse csv, (data, count) =>
+        @validator.validate data
+        expect(@validator.errors).toEqual []
+        done()


Add spec for matching parentheses.

Add spec for matching parentheses.

--- spec/bracket-matcher-spec.coffee
+++ spec/bracket-matcher-spec.coffee
@@ -107,11 +107,17 @@
         editorView.trigger "bracket-matcher:go-to-matching-bracket"
         expect(editor.getCursorBufferPosition()).toEqual [0, 28]

-    describe "when the cursor is not next to a pair", ->
-      it "moves the cursor to before the enclosing brace", ->
-        editor.setCursorBufferPosition([11, 2])
-        editorView.trigger "bracket-matcher:go-to-matching-bracket"
-        expect(editor.getCursorBufferPosition()).toEqual [0, 28]
+    describe "when the cursor is not adjacent to a pair", ->
+      describe "when within a `{}` pair", ->
+        it "moves the cursor to before the enclosing brace", ->
+          editor.setCursorBufferPosition([11, 2])
+          editorView.trigger "bracket-matcher:go-to-matching-bracket"
+          expect(editor.getCursorBufferPosition()).toEqual [0, 28]
+      describe "when within a `()` pair", ->
+        it "moves the cursor to before the enclosing brace", ->
+          editor.setCursorBufferPosition([2, 14])
+          editorView.trigger "bracket-matcher:go-to-matching-bracket"
+          expect(editor.getCursorBufferPosition()).toEqual [2, 7]

   describe "matching bracket insertion", ->
     beforeEach ->


Add failing spec

Add failing spec

--- spec/package-manager-spec.coffee
+++ spec/package-manager-spec.coffee
@@ -433,6 +433,13 @@
           runs ->
             expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0

+      describe "when setting core.packagesWithKeymapsDisabled", ->
+        it "ignores package names in the array that aren't loaded", ->
+          atom.packages.observePackagesWithKeymapsDisabled()
+
+          expect(-> atom.config.set("core.packagesWithKeymapsDisabled", ["package-does-not-exist"])).not.toThrow()
+          expect(-> atom.config.set("core.packagesWithKeymapsDisabled", [])).not.toThrow()
+
       describe "when the package's keymaps are disabled and re-enabled after it is activated", ->
         it "removes and re-adds the keymaps", ->
           element1 = createTestElement('test-1')


Use ctrl-shift-l to select grammar

Use ctrl-shift-l to select grammar

This frees up cmd-shift-l to be split selection into lines

--- keymaps/grammar-selector.cson
+++ keymaps/grammar-selector.cson
@@ -1,5 +1,5 @@
 '.platform-darwin .editor':
-  'cmd-L': 'grammar-selector:show'
+  'ctrl-L': 'grammar-selector:show'

 '.platform-win32 .editor':
   'ctrl-L': 'grammar-selector:show'


:lipstick:

:lipstick:

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -99,7 +99,7 @@

     editorDetails =
       filePath: editor.getPath()
-      lineNumber: editor.getCursorBufferPosition().toArray()[0] + 1
+      lineNumber: editor.getCursorScreenRow() + 1

   getBuilder: ->
     new LatexmkBuilder()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -183,7 +183,7 @@
 test "#setState", ->
   engine = Engine()

-  nextState
+  nextState =

   engine.setState


update generators to new utils api

update generators to new utils api

--- generators/routes.coffee
+++ generators/routes.coffee
@@ -1,5 +1,5 @@
 path = require 'path'

 module.exports = (utils, name) ->
-  utils.read(path.join('stubs', 'routes.coffee'))
-    .then (src) -> utils.write(path.join('lib', 'routes', "#{name}.coffee"), src, name: name)
+  utils.src.read(path.join('stubs', 'routes.coffee'))
+    .then (src) -> utils.target.write(path.join('lib', 'routes', "#{name}.coffee"), src, name: name)


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -33,6 +33,7 @@
   rightEnemy = GameObject
     x: 100
     y: 50
-    height: 1
+    height: 10
+

 module()


Fix typo

Fix typo

--- coffeescript/scope_fixes.coffee
+++ coffeescript/scope_fixes.coffee
@@ -7,7 +7,7 @@
   ((b) -> b = 20)()        # writes to local b

 better = ->
-  do (w=30) ->             # writes to local c
+  do (c=30) ->             # writes to local c

 bad(); okay(); better()
 console.log a, b, c        # 10 2 3


We totally do

We totally do

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -22,6 +22,7 @@
   getModel: -> @model

   generateMarkup: ->
+    # OMG we need a data-binding framework
     @classList.add "#{@model.getType()}"
     @classList.add "icon", "icon-#{@model.getIcon()}", "native-key-bindings"


避免应用程序不小心修改了 model.include 的原始设置信息

避免应用程序不小心修改了 model.include 的原始设置信息

--- lib/model.coffee
+++ lib/model.coffee
@@ -156,7 +156,7 @@
   return unless params.includes
   ret = _.filter(params.includes.split(','), (x) -> includes[x])
   return if ret.length is 0
-  _.map(ret, (x) -> includes[x])
+  _.map(ret, (x) -> _.clone(includes[x]))

 ###
 # 处理分页参数


better readable code :)

better readable code :)

--- lib/pane-split-moves-tab.coffee
+++ lib/pane-split-moves-tab.coffee
@@ -13,8 +13,10 @@
   deactivate: ->
     @disposables.dispose()

-  splitPane: ({currentTarget}) ->
+  splitPane: (e) ->
+    paneView = e.currentTarget
+    pane = paneView.getModel()
     process.nextTick ->
-      currentTarget.getModel().destroyActiveItem()
+      pane.destroyActiveItem()

 module.exports = new SplitMovesTab()


Add versal.json to the list of accepted manifest filenames

--- src/manifest.coffee
+++ src/manifest.coffee
@@ -13,7 +13,7 @@
         callback null, manifest

   lookupManifest: (dir, callback) ->
-    candidates = ['manifest.json', 'manifest.webapp', 'package.json']
+    candidates = ['versal.json', 'manifest.json', 'manifest.webapp', 'package.json']
     this.lookup candidates, dir, callback

   lookup: (paths, dir, callback) ->


Set the initial window position and size if we're either in initial state or startup.

--- src/ui/views/controller.coffee
+++ src/ui/views/controller.coffee
@@ -70,7 +70,7 @@
     # STATE_NORMAL  : conversation list on left with selected chat showing in main window
     # STATE_ABOUT   : conversation list on the left with about showing in main window
     # STATE_ADD_CONVERSATION : conversation list on the left and new / modify conversation on the main window
-    if viewstate.startup
+    if viewstate.state == viewstate.STATE_INITIAL or viewstate.startup
         if Array.isArray viewstate.size
             ipc.send 'mainwindow:setsize', viewstate.size
         #
@@ -148,7 +148,7 @@

         viewstate.startupDone()

-    if viewstate.state == viewstate.STATE_NORMAL
+    else if viewstate.state == viewstate.STATE_NORMAL
         redraw()
         applayout.lfoot controls
         applayout.listhead listhead


cleanup done

cleanup done

--- lib/provider/search-base.coffee
+++ lib/provider/search-base.coffee
@@ -39,11 +39,11 @@

   clearHighlight: ->
     @markerLayerByEditor.forEach (markerLayer) ->
-      for marker in markerLayer.getMarkers()
-        marker.destroy()
+      marker.destroy() for marker in markerLayer.getMarkers()
     @markerLayerByEditor.clear()

   highlightEditor: (editor) ->
+    # Get items shown on narrow-editor and also matching editor's filePath
     items = @ui.getNormalItemsForPath(editor.getPath())
     return unless items.length

@@ -58,18 +58,15 @@
     @subscriptions.add new Disposable => @clearHighlight()

     @subscriptions.add @ui.onDidStopRefreshing =>
-      console.log 'stop refreshing'
       @clearHighlight()
       @highlightEditor(editor) for editor in getVisibleEditors()

     @subscriptions.add @ui.onDidPreview ({editor}) =>
       unless @markerLayerByEditor.has(editor)
-        console.log 'did preview'
         @highlightEditor(editor)

     @subscriptions.add atom.workspace.onDidStopChangingActivePaneItem (item) =>
       if isTextEditor(item) and not isNarrowEditor(item) and not @markerLayerByEditor.has(item)
-        console.log 'active item changed'
         @highlightEditor(item)

     @regExpForSearchTerm = @getRegExpForSearchTerm()


Export Message

Export Message

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -6,6 +6,7 @@
   BufferedNodeProcess: require '../src/buffered-node-process'
   BufferedProcess: require '../src/buffered-process'
   GitRepository: require '../src/git-repository'
+  Message: require '../src/message'
   Point: Point
   Range: Range
   Emitter: Emitter


add new adapters

add new adapters

--- lib/adapters/hAdapters.coffee
+++ lib/adapters/hAdapters.coffee
@@ -396,4 +396,7 @@
         throw new Error "Incorrect type '#{type}'"

 exports.InboundAdapter = InboundAdapter
-exports.OutboundAdapter = OutboundAdapter+exports.OutboundAdapter = OutboundAdapter
+exports.timerAdapter = require("./hTimerAdapter")
+exports.httpInboundAdapter = require("./hHttpAdapter").HttpInboundAdapter
+exports.httpOutboundAdapter = require("./hHttpAdapter").HttpOutboundAdapter


Update es-419.coffee

Update es-419.coffee

Addeded spanish slang for Thang.
Amigos hispanohablantes, favor de opinar. ( A los mexicanos seguros nos encantará )

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -779,7 +779,7 @@
     introduction: "Mira las maneras en las que puedes involucrarte adelante y decide qué es más divertido. ¡Queremos trabajar contigo!"
     level_editor_prefix: "Usar CodeCombat"
     level_editor_suffix: "para crear y editar niveles. Los han creado niveles para sus clases, amigos, hackatones, estudiantes, familiares. Si crear un nuevo juego luce intimidante puedes ¡comenzar con base en uno nuestro!"
-    thang_editor_prefix: "Nosotros llamamos a las unidades del juego 'thangs'. Usa el"
+    thang_editor_prefix: "Nosotros llamamos a las unidades del juego 'Tiliches'. Usa el"
     thang_editor_suffix: "para modificar el arte de CodeCombat. Permite a las unidades lanzar proyectiles, altera la dirección de una animación, cambia los puntos de vida de una unidad o sube tu propio sprite de vectores."
     article_editor_prefix: "¿Ves algún error en nuestros documentos? ¿Quieres hacer algunas instrucciones para tus propias creaciones? Revisa el"
     article_editor_suffix: "y ayuda a los jugadores de CodeCombat conseguir lo más posible de su tiempo jugando."
@@ -861,7 +861,7 @@
   editor:
     main_title: "Editor de CodeCombat"
     article_title: "Editor de Artículo"
-    thang_title: "Editor de Thangs"
+    thang_title: "Editor de Tiliches"
     level_title: "Editor de Nivel"
     achievement_title: "Editor de logros"
     poll_title: "Editor de Encuesta"
@@ -884,23 +884,23 @@
     wiki: "Wiki"
     live_chat: "Chat en vivo"
     thang_main: "Principal"
-    thang_spritesheets: "Spritesheets"
+    thang_spritesheets: "Hojas de figuras( para animaciones )"
     thang_colors: "Colores"
     level_some_options: "¿Algunas opciones?"
-    level_tab_thangs: "Thangs" #Thangs is a slang for Things?
+    level_tab_thangs: "Tiliches" #Verificar cual es mejor: Cachivache, Tiliche, Artilugio, Bagatela, Cosa
     level_tab_scripts: "Scripts"
     level_tab_settings: "Opciones"
     level_tab_components: "Componentes"
     level_tab_systems: "Sistemas"
     level_tab_docs: "Documentación"
-    level_tab_thangs_title: "Thangs Actuales"
+    level_tab_thangs_title: "Tiliches Actuales"
     level_tab_thangs_all: "Todo"
     level_tab_thangs_conditions: "Condiciones Iniciales"
-    level_tab_thangs_add: "Agregar Thangs"
+    level_tab_thangs_add: "Agregar Tiliches"
     level_tab_thangs_search: "Buscar thangs"
     add_components: "Agregar Componentes"
     component_configs: "Configuraciones del Componente"
-    config_thang: "Doble clic para configurar un thang"
+    config_thang: "Doble clic para configurar un Tiliche"
     delete: "Borrar"
     duplicate: "Duplicar"
     stop_duplicate: "Parar de Duplicar"
@@ -911,7 +911,7 @@
     level_systems_tab_title: "Sistemas Actuales"
     level_systems_btn_new: "Crear Nuevo Sistema"
     level_systems_btn_add: "Agregar Sistema"
-    level_components_title: "Regresar a todos los Thangs"
+    level_components_title: "Regresar a todos los Tiliches"
     level_components_type: "Tipo"
     level_component_edit_title: "Editar Componente"
     level_component_config_schema: "Config Schema"
@@ -921,17 +921,17 @@
     new_component_title: "Crear Nuevo Componente"
     new_component_field_system: "Sistema"
     new_article_title: "Crear un Nuevo Artículo"
-    new_thang_title: "Crear un Nuevo tipo de Thang"
+    new_thang_title: "Crear un Nuevo tipo de Tiliche"
     new_level_title: "Crear un Nuevo Nivel"
     new_article_title_login: "Ingresa para Crear un Nuevo Artículo"
-    new_thang_title_login: "Ingresa para crear un nuevo tipo de Thang"
+    new_thang_title_login: "Ingresa para crear un nuevo tipo de Tiliche"
     new_level_title_login: "Ingresa para Crear un Nuevo Nivel"
     new_achievement_title: "Crear un Nuevo Logro"
     new_achievement_title_login: "Ingresa para Crear un Nuevo Logro"
     new_poll_title: "Crear una nueva encuesta"
     new_poll_title_login: "Ingresa para crear una nueva encuesta"
     article_search_title: "Buscar Artículos aquí"
-    thang_search_title: "Buscar tipos de Thang aquí"
+    thang_search_title: "Buscar tipos de Tiliche aquí"
     level_search_title: "Buscar Niveles aquí"
     achievement_search_title: "Buscar logros"
     poll_search_title: "Buscar Encuesta"
@@ -1181,13 +1181,13 @@
     systems: "Sistemas"
     component: "Componente"
     components: "Componentes"
-    thang: "Thang"
-    thangs: "Thangs"
+    thang: "Tiliche"
+    thangs: "Tiliches"
     level_session: "Tu Sesión"
     opponent_session: "Sesión del Oponente"
     article: "Artícule"
     user_names: "Nombres de usuario"
-    thang_names: "Nombres de thang"
+    thang_names: "Nombres de Tiliche"
     files: "Archivos"
     top_simulators: "Mejores simuladores"
     source_document: "Documento fuente"


fixed encoded anchor

fixed encoded anchor

--- client/store.coffee
+++ client/store.coffee
@@ -1,6 +1,6 @@
-import Vue  from 'vue'
-import Vuex from 'vuex'
-import highlight   from 'client/highlight'
+import Vue       from 'vue'
+import Vuex      from 'vuex'
+import highlight from 'client/highlight'

 Vue.use(Vuex)

@@ -23,7 +23,7 @@
         # 如果有錨點的話跳到該錨點。
         if fullPath.indexOf('#') isnt -1
             setTimeout () ->
-                id = fullPath.substring(fullPath.indexOf('#') + 1)
+                id = decodeURIComponent fullPath.substring(fullPath.indexOf('#') + 1)
                 document.querySelector('.pusher').scrollTop = document.querySelector("##{id}").getBoundingClientRect().top
             , 1

@@ -68,6 +68,8 @@
                 require ['docs/elements/step'], r
             when '/elements/container/'
                 require ['docs/elements/container'], r
+            when '/elements/divider/'
+                require ['docs/elements/divider'], r
             when '/elements/icon/'
                 require ['docs/elements/icon'], r

@@ -114,6 +116,8 @@
                 require ['docs/modules/progress'], r
             when '/modules/slider/'
                 require ['docs/modules/slider'], r
+            when '/modules/slider/javascript'
+                require ['docs/modules/slider-js'], r
             when '/modules/sidebar/'
                 require ['docs/modules/sidebar'], r
             when '/modules/sidebar/javascript/'
@@ -134,6 +138,8 @@
                 require ['docs/modules/contextmenu'], r
             when '/modules/contextmenu/javascript/'
                 require ['docs/modules/contextmenu-js'], r
+            when '/modules/scrollspy/javascript/'
+                require ['docs/modules/scrollspy-js'], r

             when '/views/speeches/'
                 require ['docs/views/speeches'], r


Update to links

Update to links

In the second to last FAQ, I corrected the education page link and set both links to open in new tabs

--- app/pages/about/faq.cjsx
+++ app/pages/about/faq.cjsx
@@ -62,7 +62,7 @@

All of our images are served to the website in random order. Unfortunately, there is no good way to see the images before and after a given image.

How can I use WildCam Gorongosa in my classroom?

-
In the spring of 2016, we will launch an educator portal that will allow students to ask ecological questions, filter trail camera data, and download the data to analyze trends. Educators can use the portal to set up their classrooms, invite students, and manage projects and data for use in class. In the meantime, we will post activities on our Education page using smaller sets of trail camera photos and data. You can also explore a variety of educational resources about Gorongosa National Park on BioInteractive.org.

+
In the spring of 2016, we will launch an educator portal that will allow students to ask ecological questions, filter trail camera data, and download the data to analyze trends. Educators can use the portal to set up their classrooms, invite students, and manage projects and data for use in class. In the meantime, we will post activities on our Education page using smaller sets of trail camera photos and data. You can also explore a variety of educational resources about Gorongosa National Park on BioInteractive.org.

How can I ask a question about a topic that’s not in this FAQ?

The Talk forum is a great place to ask questions. Our scientists will be monitoring the discussion boards and answering as many questions as we can. Many seasoned Zooniverse volunteers also offer great advice on the forum.


Some optimization

Some optimization

--- client/users.coffee
+++ client/users.coffee
@@ -138,9 +138,7 @@

-          <Show when={props.user.profile?.fullname}>
-            {props.user.profile.fullname} (
-          </Show>
+          {"#{props.user.profile.fullname} (" if props.user.profile?.fullname}
           {props.user.username}
           {', '}
           <Show when={props.user.emails?[0]?} fallback="no email">
@@ -149,15 +147,13 @@
               {" unverified"}
             </Show>
           </Show>
-          <Show when={props.user.profile?.fullname}>
-            )
-          </Show>
+          {')' if props.user.profile?.fullname}

           joined {formatDate props.user.createdAt}

-      <For each={roles()}>{({role, levels}) ->
+      {for {role, levels} in roles()
         showLevel = (i) ->
           return if i >= levels.length
           level = if levels[i] then 'YES' else 'NO'
@@ -171,16 +167,18 @@
           else
             {level}

-          <Show when={props.admin.group} fallback={showLevel 0}>
+          {if props.admin.group
             <button className="roleButton btn #{if levels[0] then 'btn-success' else 'btn-danger'}"
             onClick={[onRole, props]}>
               {showLevel 0}
             </button>
-          </Show>
+          else
+            showLevel 0
+          }
           {showLevel 1}
           {showLevel 2}

-      }</For>
+      }

     <Show when={partialMember()?.length}>
       {### Extra row to get parity right ###}
@@ -221,14 +219,14 @@
       level = -> if role in roles() then 'YES' else 'NO'
       button = ->

-          <Show when={props.admin.group} fallback={level()}>
+          <Show when={props.admin.group} fallback={-> level()}>
             <button className="roleButton btn #{btnclass()}#{if disabled() then ' disabled' else ''}"
             disabled={Boolean disabled()} onClick={[onRole, props]}>
               {level()}
             </button>
           </Show>

-      <Show when={props.admin.group and disabled()} fallback={button()}>
+      <Show when={props.admin.group and disabled()} fallback={-> button()}>
         <TextTooltip title={disabled()}>

             {button()}


unexpected()

unexpected()

--- lib/parse.coffee
+++ lib/parse.coffee
@@ -44,12 +44,12 @@
       when nextMatches /^\d+|[A-Z]$/
         consume()
       else
-        throw new Error("unexpected token \"#{peek()}\"")
+        unexpected()

   consume = (expected) ->
     token = tokens.shift()
-    if expected != undefined && expected != token
-      throw new Error("unexpected token \"#{token}\" expected \"#{expected}\"")
+    if expected? && expected != token
+      unexpected(expected)
     token

   peek = ->
@@ -58,10 +58,16 @@
   nextMatches = (re) ->
     re.test(peek())

+  unexpected = (expected) ->
+    if expected?
+      throw new Error("unexpected token #{peek()} expected #{expected}")
+    else
+      throw new Error("unexpected token #{peek()}")
+
   # we mutate tokens, so, make a copy to be polite
   tokens = _.clone tokens

   tree = Statement()
   if tokens.length != 0
-    throw new Error("unexpected token \"#{peek()}\"")
+    unexpected()
   tree


more tests.

more tests.

--- tests/tests.coffee
+++ tests/tests.coffee
@@ -127,219 +127,139 @@
   name = Mutant.toString()
   test.equal name, 'mutants'

-# ###########################
-# # TinyModel.clone()
-# ###########################
-# Tinytest.add 'TinyModel.clone( tm ) - returns a clone of Mutant', (test) ->
-#   copy = Mutant.clone( storm )
-#   test.equal copy.name, storm.name
-#   test.equal copy.createdAt, storm.createdAt
-#   test.equal copy.updatedAt, storm.updatedAt
-#
-# Tinytest.add 'TinyModel.clone( tm ) - is not persisted', (test) ->
-#   copy = Mutant.clone( storm )
-#   test.isUndefined copy._id
-#   test.isFalse copy.persisted()
-#
-# #####################
-# # TinyModel.remove()
-# #####################
-# Tinytest.add 'TinyModel.remove (with query) - destroys the Mutant', (test) ->
-#   Mutant.remove({})
-#   Mutant.insert( m_magneto )
-#   Mutant.remove( name: 'Magneto' )
-#   test.isUndefined Mutant.findOne( name: 'Magneto' )
-#
-# Tinytest.add 'TinyModel.remove (with query) - returns count', (test) ->
-#   Mutant.remove({})
-#   Mutant.insert( m_wolverine )
-#   count = Mutant.remove( name: 'Wolverine' )
-#   test.equal count, 1
-#
-# Tinytest.add 'TinyModel.remove( {} ) - destroys all Mutants', (test) ->
-#   count = Mutant.remove( {} )
-#   test.equal Mutant.all(), []
-#
-# #####################
-# # model.insert()
-# #####################
-#
-# Tinytest.add 'insert - inserts a Mutant', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_cyclops )
-#   mutant.insert()
-#   test.equal Mutant.count(), 1
-#
-# Tinytest.add 'insert - returns an id', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_cyclops )
-#   id = mutant.insert()
-#   test.matches id, /\S{17,}/
-#
-# Tinytest.add 'insert - sets timestamps', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_cyclops )
-#   id = mutant.insert()
-#   test.instanceOf mutant.createdAt, Date
-#   test.instanceOf mutant.updatedAt, Date
-#
-# Tinytest.add 'insert (invalid) - returns false', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_bo )
-#   id = mutant.insert()
-#   test.isFalse id
-#
-# Tinytest.add 'insert (invalid) - does not set timestamps', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_bo )
-#   id = mutant.insert()
-#   test.isUndefined mutant.createdAt
-#   test.isUndefined mutant.updatedAt
-#
-# Tinytest.add 'insert (persisted) - updates document', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_cyclops )
-#   id = mutant.insert()
-#   mutant.name = 'Gambit'
-#   mutant.insert()
-#   test.equal Mutant.findOne().name, 'Gambit'
-#
-# Tinytest.add 'insert (persisted, invalid) - returns false', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_cyclops )
-#   id = mutant.insert()
-#   mutant.name = 'Bo'
-#   id = mutant.insert()
-#   test.isFalse id
-#
-# Tinytest.add 'insert (persisted, invalid) - does not persist', (test) ->
-#   Mutant.remove( {} )
-#   mutant = new Mutant( m_cyclops )
-#   id = mutant.insert()
-#   mutant.name = 'Bo'
-#   id = mutant.insert()
-#   test.notEqual Mutant.findOne().name, 'Bo'
-#
-# #################
-# # model.update()
-# #################
-#
-# Tinytest.add 'update (not persisted) - return false', (test) ->
-#   Mutant.remove( {} )
-#   gambit = new Mutant
-#   gambit.name = 'Gambit'
-#   test.isFalse gambit.update()
-#
-# Tinytest.add 'update (peristed, invalid) - returns false', (test) ->
-#   Mutant.remove( {} )
-#   Mutant.insert( m_wolverine )
-#   wolverine = Mutant.findOne( name: 'Wolverine' )
-#   wolverine.name = ''
-#   test.isFalse wolverine.update()
-#
-# Tinytest.add 'update (persisted, valid) - updates document', (test) ->
-#   Mutant.remove( {} )
-#   Mutant.insert( m_wolverine )
-#   wolverine = Mutant.findOne( name: 'Wolverine' )
-#   wolverine.name = 'Cyclops'
-#   wolverine.update()
-#   test.equal Mutant.findOne().name, 'Cyclops'
-#
-# #################
-# # model.remove()
-# #################
-#
-# Tinytest.add 'remove (persisted) - removes the document', (test) ->
-#   Mutant.remove({})
-#   Mutant.insert( m_wolverine )
-#   nc = Mutant.findOne()
-#   nc.remove()
-#   test.isUndefined Mutant.findOne()
-#
-# Tinytest.add 'remove (persisted) - returns 1', (test) ->
-#   Mutant.remove({})
-#   Mutant.insert( m_magneto )
-#   m = Mutant.findOne()
-#   result = m.remove()
-#   test.equal result, 1
-#
-# Tinytest.add 'remove (not persisted) - returns false', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant
-#   mutant.name = 'NightCrawler'
-#   test.isFalse mutant.remove()
-#
-# #################
-# # model.persisted()
-# #################
-#
-# Tinytest.add 'persisted - returns true', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant( m_storm )
-#   mutant.insert()
-#   test.isTrue mutant.persisted()
-#
-# Tinytest.add 'not persisted - returns false', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant
-#   mutant.name = 'NightCrawler'
-#   test.isFalse mutant.persisted()
-#
-# #################
-# # model.isValid()
-# #################
-#
-# Tinytest.add 'isValid (valid) - returns true', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant( m_rogue )
-#   test.isTrue mutant.isValid()
-#
-# Tinytest.add 'isValid (invalid) - returns false', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant
-#   mutant.name = 'Ed'
-#   test.isFalse mutant.isValid()
-#
-# ###################
-# # model.attributes()
-# ###################
-#
-# Tinytest.add 'attributes - returns attributes', (test) ->
-#   Mutant.remove({})
-#   Mutant.insert( name: 'Rogue', leader: 'Xavier', gender: 'female' )
-#   rogue = Mutant.findOne()
-#   test.equal rogue.attributes().name, 'Rogue'
-#   test.equal rogue.attributes().leader, 'Xavier'
-#   test.equal rogue.attributes().gender, 'female'
-#
-# ###################
-# # model.hasErrors()
-# ###################
-#
-# Tinytest.add 'hasErrors (invalid) - returns true', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant
-#   mutant.isValid()
-#   test.isTrue mutant.hasErrors()
-#
-# Tinytest.add 'hasErrors (valid) - returns false', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant
-#   mutant.name = 'Xavier'
-#   test.isFalse mutant.hasErrors()
-#
-# ###################
-# # model.errorMessages()
-# ###################
-#
-# Tinytest.add 'errorMessages (invalid) - returns error', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant
-#   mutant.isValid()
-#   test.equal mutant.errorMessages(), 'name is missing, length of name must be between 5 and 50, gender is invalid'
-#
-# Tinytest.add 'errorMessages (valid) - returns empty string', (test) ->
-#   Mutant.remove({})
-#   mutant = new Mutant( m_magneto )
-#   mutant.isValid()
-#   test.equal mutant.errorMessages(), ''
+# clone
+
+Tinytest.add 'clone - returns a clone of Mutant', (test) ->
+  copy = Mutant.clone( storm )
+  test.equal copy.name, storm.name
+  test.equal copy.createdAt, storm.createdAt
+  test.equal copy.updatedAt, storm.updatedAt
+
+Tinytest.add 'clone - is not persisted', (test) ->
+  copy = Mutant.clone( storm )
+  test.isUndefined copy._id
+  test.isFalse copy.persisted()
+
+# remove
+
+Tinytest.add 'remove - destroys the Mutant', (test) ->
+  mutant = Mutant.insert( name: 'Magento', gender: 'male' )
+  test.isTrue mutant.persisted()
+  Mutant.remove( name: 'Magneto' )
+  test.isUndefined Mutant.findOne( name: 'Magneto' )
+
+# model.insert
+
+Tinytest.add 'model.insert - inserts a Mutant', (test) ->
+  colossus = new Mutant( name: 'Colossus', gender: 'male' )
+  colossus.insert()
+  mutant = Mutant.findOne( name: 'Colossus' )
+  test.equal mutant.name, colossus.name
+  test.matches mutant._id, /\S{17,}/
+  test.instanceOf mutant.createdAt, Date
+  test.instanceOf mutant.updatedAt, Date
+
+Tinytest.add 'model.insert (invalid) - returns false', (test) ->
+  mutant = new Mutant( name: '' )
+  id = mutant.insert()
+  test.isFalse id
+
+Tinytest.add 'model.insert (invalid) - does not set timestamps', (test) ->
+  mutant = new Mutant( name: '' )
+  mutant.insert()
+  test.isUndefined mutant.createdAt
+  test.isUndefined mutant.updatedAt
+
+Tinytest.add 'model.insert (persisted) - updates document', (test) ->
+  mutant = new Mutant( name: 'NightCrawler', gender: 'male' )
+  id = mutant.insert()
+  mutant.name = 'Gambit'
+  mutant.insert()
+  test.equal Mutant.findOne( _id: id ).name, 'Gambit'
+
+Tinytest.add 'model.insert (persisted, invalid) - fails', (test) ->
+  mutant = new Mutant( name: 'Cyclops', gender: 'male' )
+  id = mutant.insert()
+  mutant.name = ''
+  test.isFalse mutant.insert()
+  test.equal Mutant.findOne( _id: id ).name, 'Cyclops'
+
+# model.update
+
+Tinytest.add 'model.update (not persisted) - return false', (test) ->
+  gambit = new Mutant
+  gambit.name = 'Gambit'
+  test.isFalse gambit.update()
+
+Tinytest.add 'model.update (persisted, invalid) - returns false', (test) ->
+  rogue = Mutant.insert( name: 'Rogue', gender: 'female' )
+  rogue.name = ''
+  test.isFalse rogue.update()
+
+Tinytest.add 'model.update (persisted, valid) - updates document', (test) ->
+  ragnar = Mutant.insert( name: 'Ragnar', gender: 'male' )
+  ragnar.name = 'Clown'
+  ragnar.update()
+  test.equal Mutant.findOne( _id: ragnar._id ).name, 'Clown'
+
+# model.remove
+
+Tinytest.add 'model.remove (persisted) - removes the document', (test) ->
+  spiderman = Mutant.insert( name: 'Spiderman', gender: 'male' )
+  result = spiderman.remove()
+  test.isUndefined Mutant.findOne( _id: spiderman._id )
+  test.equal result, 1
+
+Tinytest.add 'model.remove (not persisted) - returns false', (test) ->
+  mutant = new Mutant( name: 'Robert' )
+  test.isFalse mutant.remove()
+
+# model.persisted
+
+Tinytest.add 'persisted - returns true', (test) ->
+  mutant = Mutant.findOne()
+  test.isTrue mutant.persisted()
+
+Tinytest.add 'persisted (new) - returns false', (test) ->
+  mutant = new Mutant( name: 'Alexander' )
+  test.isFalse mutant.persisted()
+
+# model.isValid
+
+Tinytest.add 'isValid (valid) - returns true', (test) ->
+  mutant = new Mutant( name: 'Tobias', gender: 'male' )
+  test.isTrue mutant.isValid()
+
+Tinytest.add 'isValid (invalid) - returns false', (test) ->
+  mutant = new Mutant( name: 'Z' )
+  test.isFalse mutant.isValid()
+
+# model.attributes
+
+Tinytest.add 'attributes - returns attributes', (test) ->
+  mutant = Mutant.findOne( name: 'Wolverine' )
+  test.equal mutant.attributes().name, 'Wolverine'
+  test.equal mutant.attributes().gender, 'male'
+
+# model.hasErrors
+
+Tinytest.add 'hasErrors (invalid) - returns true', (test) ->
+  mutant = new Mutant
+  mutant.isValid()
+  test.isTrue mutant.hasErrors()
+
+Tinytest.add 'hasErrors (valid) - returns false', (test) ->
+  mutant = new Mutant( name: 'George', gender: 'male' )
+  test.isFalse mutant.hasErrors()
+
+# model.errorMessages
+
+Tinytest.add 'errorMessages (invalid) - returns error', (test) ->
+  mutant = new Mutant
+  mutant.isValid()
+  test.equal mutant.errorMessages(), 'name is missing, length of name must be between 5 and 50, gender is invalid'
+
+Tinytest.add 'errorMessages (valid) - returns empty string', (test) ->
+  mutant = new Mutant( name: 'Gregory', gender: 'female' )
+  mutant.isValid()
+  test.equal mutant.errorMessages(), ''


add a configurable doc index

add a configurable doc index

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -40,6 +40,7 @@
 # the project. Use one of these instead of hardcoding the defaults!
 # See the task definitions below for information on defaults usage.
 DEFAULT_LIBRARY = "library"
+DEFAULT_PREAMBLE = "README.md"
 DEFAULT_ARTIFACTS = "artifacts"
 DEFAULT_DOCUMENTS = "documents"
 DEFAULT_SCOPING = "development"
@@ -58,6 +59,7 @@
     # options are shared among all of the tasks and the entire file!
     # Please refer to `Cakefile` and `CoffeeScript` for information.
     option "-l", "--library [PATH]", "Path to the library sources"
+    option "-p", "--preamble [PATH]", "An index file to use for docs"
     option "-a", "--artifacts [PATH]", "Path to the artifacts directory"
     option "-d", "--documents [PATH]", "Path to the documents directory"
     option "-s", "--scoping [SCOPE]", "The name of the scope to boot kernel"
@@ -72,8 +74,9 @@
     # Please see the implementation for some of the important details
     task "documents", "generate the library documentation", (options) ->
         assert library = options.library or DEFAULT_LIBRARY
+        assert preamble = option.preamble or DEFAULT_PREAMBLE
         assert documents = options.documents or DEFAULT_DOCUMENTS
-        [pattern, index] = ["#{library}/**/*.coffee", "README.md"]
+        [pattern, index] = ["#{library}/**/*.coffee", preamble]
         parameters = [pattern, "Cakefile", index, "-o", documents]
         parameters.push "--github" if g = "git-hub-pages" of options
         logger.info "Publishing docs to GitHub pages".yellow if g


Missing handler for page title update

Missing handler for page title update

--- stores/ApplicationStore.coffee
+++ stores/ApplicationStore.coffee
@@ -3,9 +3,11 @@
 createStore = require('fluxible/utils/createStore')
 routesConfig = require('../configs/routes')

-ApplicationStore = createStore(
+ApplicationStore = createStore
   storeName: 'ApplicationStore'
-  handlers: 'CHANGE_ROUTE_SUCCESS': 'handleNavigate'
+  handlers:
+    'CHANGE_ROUTE_SUCCESS': 'handleNavigate'
+    'UPDATE_PAGE_TITLE': 'updatePageTitle'

   initialize: ->
     @currentPageName = null
@@ -24,7 +26,10 @@
     @currentPage = page
     @currentRoute = route
     @emitChange()
-    return
+
+  updatePageTitle: (title) ->
+    @pageTitle = title.pageTitle
+    @emitChange()

   getCurrentPageName: ->
     @currentPageName
@@ -53,7 +58,5 @@
     @pages = state.pages
     @currentRoute = state.route
     @pageTitle = state.pageTitle
-    return
-)

 module.exports = ApplicationStore


:art: Cleanup

:art: Cleanup

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -172,6 +172,7 @@
                     new Point(lineNumber, column),
                     new Point(lineNumber, column)),
                     invalidate: 'touch'
+
                 labelElement = document.createElement('div')
                 labelElement.textContent = keyLabel
                 labelElement.style.fontSize = fontSize
@@ -181,6 +182,7 @@
                 labelElement.classList.add 'jumpy-label'
                 if highContrast
                     labelElement.classList.add 'high-contrast'
+
                 decoration = editor.decorateMarker marker,
                     type: 'overlay'
                     item: labelElement


refactor class i18n

refactor class i18n

--- app/src/i18n/i18n.coffee
+++ app/src/i18n/i18n.coffee
@@ -4,8 +4,16 @@
   @chromeStore: undefined
   # Contain all the translation files
   @translations: {}
+  #
+  @favLang:
+    memoryValue: undefined
+    syncStoreValue: undefined
+    chromeStoreValue: undefined
+    syncStoreIsSet: undefined
+    chromeStoreIsSet: undefined
+
   # User favorite language
-  @userFavLang: undefined
+  #@userFavLang: undefined
   # User favorite language and region
   @userFavLocale: undefined
   # Languages + regions tags that represent the user's Chrome browser preferences
@@ -15,55 +23,97 @@
   # Supported languages by the app (when translation is done)
   @Languages: {}
   # [Boolean] If userFavLang is set into chromeStore or not
-  @userFavLangChromeStoreIsSet: undefined
+  #@userFavLangChromeStoreIsSet: undefined
   # [Boolean] If userFavLocale is set into chromeStore or not
   @userFavLocaleChromeStoreIsSet: undefined
   # [Boolean] If userFavLang is set into syncStore or not
-  @userFavLangSyncStoreIsSet: undefined
+  #@userFavLangSyncStoreIsSet: undefined
   # [Boolean] If userFavLocale is set into syncStore or not
   @userFavLocaleSyncStoreIsSet: undefined

+
+
   @init: (cb) =>

     @chromeStore = new ledger.storage.ChromeStore('i18n')
+
+    @favLang

     # Know about the supported languages and load the translation files
     @Languages = Object.keys(ledger.i18n.Languages)
     for tag in @Languages
       @loadTrad(tag)

-
-    # Manage text translation
-    @checkBoolUserFavLang()
-    .then =>
-      @loadUserFavLang() # @userFavLang <- Stores
-    .catch (err) =>
-      l err
-      @setUserFavLang() # @userFavLang -> Stores
-
-    #.then ->
-      # check if sync store === chrome store
-    .done()
-
-
-    # Manage date, time and currency converters
-    @checkBoolLocale()
-    .then =>
-      @loadLocale() # @userFavLocale <- Stores
-    .catch (err) =>
-      l err
-      @setLocale() # @userFavLocale -> Stores
-
-    #.then ->
-      # check if sync store === chrome store
-    .done()
+    initLangAndLocale = () =>
+      # Manage text translation
+      @checkBoolUserFavLang() # is Lang set into one of the store ?
+      .then (bool) =>
+        l bool, '@loadUserFavLang'
+        @loadUserFavLang(bool) # @userFavLang <- Stores
+      .catch (err) =>
+        l err
+        @setUserFavLangToStore() # @userFavLang -> Stores
+      .finally =>
+        @checkUserFavLangSyncStoreEqualsChromeStore()
+      .done()
+
+
+      # Manage date, time and currency converters
+      @checkBoolLocale() # is Locale set into one of the store ?
+      .then (bool) =>
+        l bool, '@loadLocale'
+        @loadLocale() # @userFavLocale <- Stores
+      .then =>
+        @checkLangAndLocaleCorrespondance()
+      .catch (err) =>
+        l err
+        @setLocaleToStore() # @userFavLocale -> Stores
+        @checkLangAndLocaleCorrespondance()
+
+      #.then ->
+        # check if sync store === chrome store
+      .done()
+
+    initLangAndLocale()
+    ledger.app.on('wallet:initialized', initLangAndLocale)

     cb()

   # General ####
+
+  ###
+    Check if sync Store values equals Chrome store values
+  ###
+  @checkUserFavLangSyncStoreEqualsChromeStore: () ->
+    deferred = Q.defer()
+
+    if ledger.storage.sync?
+      ledger.i18n.userFavLang.syncValue = undefined
+      ledger.i18n.userFavLang.chromeValue = undefined
+      ledger.storage.sync.get 'i18n_userFavLang', (r) ->
+        if Array.isArray(r.i18n_userFavLang)
+          r.i18n_userFavLang = r.i18n_userFavLang[0]
+        l r.i18n_userFavLang
+        userFavLangSync = r.i18n_userFavLang
+        deferred.resolve()
+      @chromeStore.get 'i18n_userFavLang', (r) ->
+        if Array.isArray(r.i18n_userFavLang)
+          r.i18n_userFavLang = r.i18n_userFavLang[0]
+        l r.i18n_userFavLang
+        userFavLangChrome = r.i18n_userFavLang
+        l userFavLangChrome
+        deferred.resolve()
+
+      l ledger.i18n.userFavLangSync
+      l ledger.i18n.userFavLangChrome
+      l ledger.i18n.userFavLangSync == ledger.i18n.userFavLangChrome
+
+    return deferred.promise
+
+

   ###
     Load user language of his Chrome browser UI version into @browserUiLang
@@ -73,7 +123,7 @@

   ###
-    Get user favorite languages with regions set in his Chrome browser preferences and store it in @acceptLanguages variable - Async call
+    Get user favorite languages with regions set in his Chrome browser preferences and store it in @browserAcceptLanguages variable

     @return [Promise] promise Promise containing the user favorite languages with regions
   ###
@@ -81,7 +131,7 @@
     deferred = Q.defer()
     chrome.i18n.getAcceptLanguages (requestedLocales) =>
       @browserAcceptLanguages = requestedLocales
-      deferred.resolve(requestedLocales)
+      deferred.resolve()

     return deferred.promise

@@ -89,9 +139,10 @@
   ###
     Set @userFavLang from browser accept languages
   ###
-  @setUserFavLangFromBrowserAcceptLanguages: () ->
+  @setUserFavLangToStoreFromBrowserAcceptLanguages: () ->
     # Select language tag without region
     i = 0
+    l ledger.i18n.browserAcceptLanguages
     for str in ledger.i18n.browserAcceptLanguages
       if ledger.i18n.browserAcceptLanguages[i].length > 2
         i++
@@ -107,19 +158,20 @@

     @param [String] tag Codified (BCP 47) language tag - Official list here : http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
   ###
-  @setUserFavLang: (tag) =>
+  @setUserFavLangToStore: (tag) =>
     # If tag language is set manually
     if tag?
       if tag.length > 2
-        throw new Error 'Tag language must be two characters. Use ledger.i18n.setLocale() if you want to set the region'
+        throw new Error 'Tag language must be two characters. Use ledger.i18n.setLocaleToStore() if you want to set the region'
       @userFavLang = tag
     else
       @loadUserBrowserUiLang()
       @loadUserBrowserAcceptLangs()
-      .then(() -> @setUserFavLangFromBrowserAcceptLanguages)
+      .then(@setUserFavLangToStoreFromBrowserAcceptLanguages())
       .catch (err) -> l(err)
       .done()
       tag ?= @userFavLang || @browserUiLang
+      l tag
       @userFavLang = tag

     # set tag language to one of the store
@@ -168,23 +220,20 @@

     if ledger.storage.sync?
       ledger.storage.sync.get 'i18n_userFavLang', (r) =>
-        #l r.i18n_userFavLang
         if r.i18n_userFavLang isnt undefined
           @userFavLangSyncStoreIsSet = true
-          deferred.resolve()
+          deferred.resolve('@userFavLang ' + r.i18n_userFavLang + ' is set into syncStore')
         else
           @userFavLangSyncStoreIsSet = false
-          deferred.reject()
+          deferred.reject('@userFavLang is not set into syncStore')
     else
       @chromeStore.get 'i18n_userFavLang', (r) =>
-        #l r.i18n_userFavLang
         if r.i18n_userFavLang isnt undefined
           @userFavLangChromeStoreIsSet = true
-          deferred.resolve()
+          deferred.resolve('@userFavLang ' + r.i18n_userFavLang + ' is set into chromeStore')
         else
-          l '@userFavLang is not set neither into synced Store or chrome store'
           @userFavLangChromeStoreIsSet = false
-          deferred.reject()
+          deferred.reject('@userFavLang is not set neither into synced Store or chrome store')

     return deferred.promise

@@ -207,30 +256,45 @@

+  ###
+    Check if userFavLocale corresponds to userFavLang
+  ###
+  @checkLangAndLocaleCorrespondance: () =>
+    deferred = Q.defer()
+    if @userFavLocale.substr(0, 2) isnt @userFavLang
+      @userFavLocale = @userFavLang
+      deferred.reject('Lang and Locale correspondence was not correct')
+    return deferred.promise
+
+
   # User Favorite Locale ####

   ###
-    Set user locale (region)
+    Set user locale (region) to one of the stores

     @param [String] tag Codified (BCP 47) language tag - Official list here : http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
   ###
-  @setLocale: (tag) =>
+  @setLocaleToStore: (tag) =>
     # If tag language is set manually
     if tag?
-      if tag.length < 5
-        throw new Error 'Tag language must be at least five characters. Use ledger.i18n.setUserFavLang() if you want to set the language without the region'
+      #if tag.length < 5
+      #  throw new Error 'Tag language must be at least five characters. Use ledger.i18n.setUserFavLang() if you want to set the language without the region'
       if tag.substr(0, 2) isnt @userFavLang
         throw new Error 'You cannot set a locale which does not correspond to the user favorite language'
       @userFavLocale = tag
     else
       tag ?= @userFavLocale || @browserUiLang
+      #l @userFavLocale
+      #l tag
       @userFavLocale = tag

     # set tag language to one of the store
     if ledger.storage.sync?
+      l 'locale set to sync store'
       ledger.storage.sync.set({i18n_userFavLocale: tag})
       @checkBoolLocale()
     else
+      l 'locale set to chrome store'
       ledger.i18n.chromeStore.set({i18n_userFavLocale: tag})
       @checkBoolLocale()

@@ -275,23 +339,23 @@
     deferred = Q.defer()
     if ledger.storage.sync?
       ledger.storage.sync.get 'i18n_userFavLocale', (r) =>
-        #l r.i18n_userFavLocale
+        l 'sync store: userFavLocale', r.i18n_userFavLocale
         if r.i18n_userFavLocale isnt undefined
           @userFavLocaleSyncStoreIsSet = true
-          deferred.resolve()
+          deferred.resolve('@userFavLocale is set into synced Store')
         else
           @userFavLocaleSyncStoreIsSet = false
-          deferred.reject()
+          deferred.reject('@userFavLocale is not set into synced Store')
     else
       @chromeStore.get 'i18n_userFavLocale', (r) =>
-        #l r.i18n_userFavLocale
+        #l 'chrome store: userFavLocale', r.i18n_userFavLocale
         if r.i18n_userFavLocale isnt undefined
           @userFavLocaleChromeStoreIsSet = true
-          deferred.resolve()
+          deferred.resolve('@userFavLocale ' + r.i18n_userFavLocale + ' is set into chrome Store')
         else
           @userFavLocaleChromeStoreIsSet = false
           l '@userFavLocale is not set neither into synced Store or chrome store'
-          deferred.reject()
+          deferred.reject(false)

     return deferred.promise


fix progress bar

fix progress bar

--- client/views/config.coffee
+++ client/views/config.coffee
@@ -68,8 +68,9 @@
     AWSConfigTestDone.set(0)
     bootbox.dialog message: window.renderTmp(Template.ConfigAWSTestDialog)
     Meteor.call("verify_AWS_configuration", on_verify_callback)
-    window.setTimeout(->
-      AWSConfigTestDone.set(30)
+    Meteor.setTimeout(->
+      if AWSConfigTestDone.get() < 100
+        AWSConfigTestDone.set(30)
     ,
       300)


removing newline character from issue title so it will find duplicate issues on github

--- lib/notification-issue.coffee
+++ lib/notification-issue.coffee
@@ -60,11 +60,8 @@
     encodeURI(str).replace(/#/g, '%23').replace(/;/g, '%3B').replace(/%20/g, '+')

   getIssueTitle: ->
-    offset = 3
     title = @notification.getMessage()
     title = title.replace(process.env.ATOM_HOME, '$ATOM_HOME')
-    offset = 4 if title.indexOf('\n') > -1
-    title = title.replace(/\r?\n|\r/g,"");
     if process.platform is 'win32'
       title = title.replace(process.env.USERPROFILE, '~')
       title = title.replace(path.sep, path.posix.sep) # Standardize issue titles
@@ -72,8 +69,8 @@
       title = title.replace(process.env.HOME, '~')

     if title.length > TITLE_CHAR_LIMIT
-      title = title.substring(0, TITLE_CHAR_LIMIT - offset) + '...'
-    title
+      title = title.substring(0, TITLE_CHAR_LIMIT - 3) + '...'
+    title.replace(/\r?\n|\r/g,"");

   getIssueBody: ->
     new Promise (resolve, reject) =>


emitting a warning if a font can’t be loaded


Fix geometry functions

Fix geometry functions

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -172,6 +172,9 @@
 Utils.devicePixelRatio = ->
 	window.devicePixelRatio

+Utils.pathJoin = ->
+	Utils.arrayFromArguments(arguments).join("/")
+
 ######################################################
 # MATH FUNCTIONS

@@ -232,6 +235,20 @@

 	script

+Utils.domLoadScriptSync = (path) ->
+
+	xhrObj = new XMLHttpRequest();
+	xhrObj.open('GET', path, false);
+	xhrObj.send('');
+
+	se = document.createElement('script')
+	se.type = "text/javascript"
+	se.text = "window.__import__ = " + xhrObj.responseText
+
+	document.getElementsByTagName('head')[0].appendChild(se)
+
+	return window.__import__
+
 ######################################################
 # GEOMERTY FUNCTIONS

@@ -249,6 +266,29 @@
 		x: _.max point.map (size) -> size.x
 		y: _.max point.map (size) -> size.y

+Utils.pointDistance = (pointA, pointB) ->
+	distance =
+		x: Math.abs(pointB.x - pointA.x)
+		y: Math.abs(pointB.y - pointA.y)
+
+Utils.pointInvert = (point) ->
+	point =
+		x: 0 - point.x
+		y: 0 - point.y
+
+Utils.pointTotal = (point) ->
+	point.x + point.y
+
+Utils.pointAbs = (point) ->
+	point =
+		x: Math.abs point.x
+		y: Math.abs point.y
+
+Utils.pointInFrame = (point, frame) ->
+	return false  if point.x < frame.minX or point.x > frame.maxX
+	return false  if point.y < frame.minY or point.y > frame.maxY
+	true
+
 # Size

 Utils.sizeMin = ->
@@ -319,83 +359,31 @@

 	frame

-
-
-
-
-# Points
-
-# Utils.pointDistance = (pointA, pointB) ->
-# 	distance =
-# 		x: Math.abs(pointB.x - pointA.x)
-# 		y: Math.abs(pointB.y - pointA.y)
-
-# Utils.pointInvert = (point) ->
-# 	point =
-# 		x: 0 - point.x
-# 		y: 0 - point.y
-
-# Utils.pointTotal = (point) ->
-# 	point.x + point.y
-
-
-
-# Utils.pointAbs = (point) ->
-# 	point =
-# 		x: Math.abs point.x
-# 		y: Math.abs point.y
-
-# Utils.pointInFrame = (point, frame) ->
-# 	return false  if point.x < frame.minX or point.x > frame.maxX
-# 	return false  if point.y < frame.minY or point.y > frame.maxY
-# 	true
-
-# Utils.convertPoint = (point, view1, view2) ->
-
-# 	# Public: Convert a point between two view coordinate systems
-# 	#
-# 	# point - The point to be converted
-# 	# view1 - The origin view of the point
-# 	# view2 - The destination view of the point
-# 	#
-# 	# Returns an Object
-# 	#
-
-# 	point = _.clone point
-
-# 	traverse = (view) ->
-
-# 		currentView = view
-# 		superViews = []
-
-# 		while currentView and currentView.superLayer
-# 			superViews.push currentView.superLayer
-# 			currentView = currentView.superLayer
-
-# 		return superViews
-
-# 	superViews1 = traverse view1
-# 	superViews2 = traverse view2
-
-# 	superViews2.push view2 if view2
-
-# 	for view in superViews1
-# 		point.x += view.x
-# 		point.y += view.y
-
-# 		if view.scrollFrame
-# 			point.x -= view.scrollFrame.x
-# 			point.y -= view.scrollFrame.y
-
-# 	for view in superViews2
-# 		point.x -= view.x
-# 		point.y -= view.y
-
-# 		if view.scrollFrame
-# 			point.x += view.scrollFrame.x
-# 			point.y += view.scrollFrame.y
-
-# 	return point
+# Coordinate system
+
+Utils.convertPoint = (input, layerA, layerB) ->
+
+	# Convert a point between two layer coordinate systems
+
+	point = {}
+
+	for k in ["x", "y", "width", "height"]
+		point[k] = input[k]
+
+	superLayersA = layerA?.superLayers() or []
+	superLayersB = layerB?.superLayers() or []
+
+	superLayersB.push layerB if layerB
+
+	for layer in superLayersA
+		point.x += layer.x - layer.scrollFrame.x
+		point.y += layer.y - layer.scrollFrame.y
+
+	for layer in superLayersB
+		point.x -= layer.x + layer.scrollFrame.x
+		point.y -= layer.y + layer.scrollFrame.y
+
+	return point

 _.extend exports, Utils


Let the editor jump to the first error line

--- lib/pawn-build.coffee
+++ lib/pawn-build.coffee
@@ -49,8 +49,11 @@
     # get path of currently open file
     filepath = atom.workspace.getActiveTextEditor()?.getPath()

+
     # only continue if file is a .pwn file
     return unless filepath? and path.extname(filepath) == '.pwn'
+    #also save it, so the unsaved buffer is included
+    atom.workspace.getActiveTextEditor().save()

     # prepare command and arguments from package settigns
     cmd = atom.config.get('pawn-build.pawnExecutablePath')
@@ -84,11 +87,13 @@
       output = @createOutputPanel()

       if data
-        # show output of pawncc
+        #Jump to the first error line
         editor = atom.workspace.getActiveTextEditor()
         row = data.match(/\(([^)]+)\)/)[1]
-        if row
+        if(isFinite(row))
           editor.setCursorBufferPosition([row,0])
+
+        # show output of pawncc
         lines = data.split('\n')
         for line in lines
           output.add new PlainMessageView


:lipstick: Rename options as initial

:lipstick: Rename options as initial

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -898,10 +898,10 @@
     handleOffset = handle.offset()
     dragOffset = handleOffset.top - e.pageY

-    options = {row, handle, handleHeight, dragOffset}
-
-    @body.on 'mousemove', stopPropagationAndDefault (e) => @rowResizeDrag(e, options)
-    @body.on 'mouseup', stopPropagationAndDefault (e) => @endRowResizeDrag(e, options)
+    initial = {row, handle, handleHeight, dragOffset}
+
+    @body.on 'mousemove', stopPropagationAndDefault (e) => @rowResizeDrag(e, initial)
+    @body.on 'mouseup', stopPropagationAndDefault (e) => @endRowResizeDrag(e, initial)

     rulerTop = @getScreenRowOffsetAt(row) + @getScreenRowHeightAt(row)


Abstracting file write

Abstracting file write

--- lib/timedevent.coffee
+++ lib/timedevent.coffee
@@ -22,14 +22,20 @@
     e.id = l[1]
     e.event = l[2]
     @add(e)
+  write_file: (fn,do_write) ->
+    f = new File(fn)
+    f.open("write,create", "text")
+    do_write(f)
+    # f.remove() if f.exists
+    f.close()
   write: (fn) ->
-    file = new File(fn)
-    # file.remove() if file.exists
-    file.open("write,create", "text")
-    file.writeln("timed_events = [")
-    for rec in @list
-      [date,e] = rec
-      # file.writeln(date.valueOf())
-      file.writeln('["',e.time,'","',e.id,'","',e.event,'"],')
-    file.writeln("]")
-    file.close()
+    list = @list
+    @write_file(fn, (f) ->
+      f.writeln("timed_events = [")
+      for rec in list
+        [date,e] = rec
+        # f.writeln(date.valueOf())
+        f.writeln('["',e.time,'","',e.id,'","',e.event,'"],')
+      f.writeln("]")
+    )
+


创建模块方法独立到Util中

创建模块方法独立到Util中

--- lib/utils/util.coffee
+++ lib/utils/util.coffee
@@ -69,6 +69,43 @@
       moduleName: info.appName
       moduleId: info.appId.split('.').slice(-1)[0]
     @formatModuleConfigToObj opt
+
+
+  createModule: (params,cb) ->
+    pages = params.builderConfig
+    moduleConfig = params.moduleConfig
+    moduleInfo = params.moduleInfo
+    modulePath = pathM.join moduleInfo.modulePath,moduleInfo.identifier
+    moduleConfigPath = pathM.join modulePath,desc.moduleConfigFileName
+    @createDir modulePath, (err) =>
+      return cb err if err?
+      @writeJson moduleConfigPath, moduleConfig, (err) =>
+        return cb err if err?
+        console.log 'writeJson Success!'
+        if pages.length is 0
+          htmlFilePath = pathM.join modulePath,desc.mainEntryFileName
+          htmlString = Util.getIndexHtmlCore moduleConfig
+          @writeFile htmlFilePath, htmlString, (err) =>
+            cb err
+        else
+          len = pages.length
+          currIndex = 0
+          getPageInfo = (index) =>
+            page = pages[index]
+            result =
+              html: page.html
+              path: pathM.join moduleInfo.modulePath,page.name
+            return result
+          info = getPageInfo currIndex
+          writeHtmlFileCB = (err) =>
+            return cb err if err?
+            if currIndex > len
+              cb()
+            else
+              info = getPageInfo --currIndex
+              @writeFile info.path, info.html, writeHtmlFileCB
+          @writeFile info.path, info.html, writeHtmlFileCB
+

   # 将传递过来的 str 进行判断是否符合文件命名，如果不符合，将不符合的字符改为"-", 并进行去重
   checkProjectName: (str)->


Looks like it was an Atom bug and not an off-by-one error.

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -29,7 +29,7 @@
     ignoreCurLine = atom.config.get('whitespace.ignoreWhitespaceOnCurrentLine')

     buffer.scan /[ \t]+$/g, ({lineText, match, replace}) ->
-      whitespaceRow = buffer.positionForCharacterIndex(match.index-1).row
+      whitespaceRow = buffer.positionForCharacterIndex(match.index).row
       cursorRow = editor.getCursor().getBufferRow()

       return if ignoreCurLine and whitespaceRow is cursorRow


added working pagination

added working pagination

--- lib/component-dataTable.coffee
+++ lib/component-dataTable.coffee
@@ -79,14 +79,14 @@
   return @getData().options or @presetOptions() or false

 Template.dataTable.fnServerData = ( sSource, aoData, fnCallback, oSettings ) ->
-  @log 'fnServerData:context', @
+  aoData = @arrayToDictionary aoData, 'name'
   @log 'fnServerData:aoData', aoData
-  @log 'fnServerData:oSettings', oSettings
-  aoData = @arrayToDictionary aoData, 'name'
   callbackOptions =
-    $skip: aoData.iDisplayStart.value
-    $limit: aoData.iDisplayLength.value
+    skip: aoData.iDisplayStart.value
+    limit: aoData.iDisplayLength.value
   callbackCursor = @getCollection().find @getQuery(), callbackOptions
+  aaData = callbackCursor.fetch()
+  @log 'fnServerData:aaData', aaData
   fnCallback
     # An unaltered copy of sEcho sent from the client side.
     # This parameter will change with each draw (it is basically a draw count)
@@ -95,9 +95,9 @@
     iTotalRecords: @getCollection().find().count()
     # Total records, after filtering (i.e. the total number of records after filtering has been applied
     # not just the number of records being returned in this result set)
-    iTotalDisplayRecords: callbackCursor.count()
+    iTotalDisplayRecords: @getCollection().find().count()
     # The data in a 2D array. Note that you can change the name of this parameter with sAjaxDataProp.
-    aaData: callbackCursor.fetch()
+    aaData: aaData

 # Prepares the options object by merging the options passed in with the defaults
 Template.dataTable.prepareOptions = ->


Term coefficients should be floats, not strings.

--- js/inequality.coffee
+++ js/inequality.coffee
@@ -6,7 +6,8 @@

 # A non-normalised inequality is made up of multiple terms
 class Term
-  constructor: (@coefficient, @variable) ->
+  constructor: (coefficient, @variable) ->
+    @coefficient = parseFloat coefficient

   toString: ->
     c = "#{@coefficient}"


Minor fix for js in parens

Minor fix for js in parens

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -600,6 +600,22 @@
         ]
       }
       {
+        applyEndPatternLast: 1
+        begin: "\\("
+        beginCaptures:
+          "0":
+            name: "punctuation.section.scope.begin.js"
+        end: "\\)"
+        endCaptures:
+          "0":
+            name: "punctuation.section.scope.end.js"
+        patterns: [
+          {
+            include: "#js"
+          }
+        ]
+      }
+      {
         include: "#expression"
       }
       {


:lipstick:

:lipstick:

--- lib/grammar-status-view.coffee
+++ lib/grammar-status-view.coffee
@@ -14,7 +14,7 @@
       @updateGrammarText()

     atom.config.observe 'grammar-selector.right', =>
-      @.attach()
+      @attach()

     @subscribe this, 'click', ->
       atom.workspaceView.trigger('grammar-selector:show')


Extract reading dom and creating dummy in specific methods

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -114,29 +114,35 @@
   getDefaultColor: ->
     @defaultColor ||= @transparentize(@minimapView.editorView.css('color'), @getTextOpacity())

-  retrieveTokenColorFromDom: (token)->
-    # This function insert a dummy token element in the DOM compute its style,
-    # return its color property, and remove the element from the DOM.
-    # This is quite an expensive operation so results are cached in getTokenColor.
-    # Note: it's probably not the best way to do that, but that's the simpler approach I found.
+  ensureDummyNodeExistence: ->
     unless @dummyNode?
       @dummyNode = document.createElement('span')
       @dummyNode.style.visibility = 'hidden'
       @editorView.append(@dummyNode)

+  # This function insert a dummy token element in the DOM to compute its style,
+  # return the specified property, and remove the element from the DOM.
+  retrieveStyleFromDom: (scopes, property) ->
+    @ensureDummyNodeExistence()
+
     parent = @dummyNode
-    for scope in token.scopes
+    for scope in scopes
       node = document.createElement('span')
-      # css class is the token scope without the dots,
+      # css class is the scope without the dots,
       # see pushScope @ atom/atom/src/lines-component.coffee
       node.className = scope.replace(/\.+/g, ' ')
-      if parent
-        parent.appendChild(node)
+      parent.appendChild(node) if parent?
       parent = node

-    color = @transparentize(getComputedStyle(parent).getPropertyValue('color'), @getTextOpacity())
+    value = getComputedStyle(parent).getPropertyValue(property)
     @dummyNode.innerHTML = ''
-    color
+
+    value
+
+  retrieveTokenColorFromDom: (token)->
+    # This is quite an expensive operation so results are cached in getTokenColor.
+    color = @retrieveStyleFromDom(token.scopes, 'color')
+    @transparentize(color, @getTextOpacity())

   getTokenColor: (token)->
     #Retrieve color from cache if available


better error handling, Link to GMail

better error handling, Link to GMail

--- src/mailButler.appsscript.coffee
+++ src/mailButler.appsscript.coffee
@@ -5,8 +5,17 @@
   @NO_ACTION:             'NoActionSpecified'
   @STORE_FAILED:          'StoringFailed'

-  @toReadable: (code) ->
-    i18n.get "error#{code}"
+  @toReadable: (code, map) ->
+    i18n.get "error#{code}", map
+
+class Error
+  constructor: (@code, @map) ->
+
+  getLocalisedMessage: ->
+    ErrorCodes.toReadable @code, @map
+
+  toString: ->
+    @getLocalisedMessage()

 class i18n
   @messages:
@@ -20,9 +29,9 @@
       butSomethingWentWrong:      'But something went wrong: $status$'

       # The following keys must not be changed - they are "error" + ErrorCodes.KEY
-      errorMessageIdInvalid:      'Given message ID is not valid'
+      errorMessageIdInvalid:      """Given message ID is not valid. Click here to open the according email."""
       errorNoScheduleTime:        'No scheduling time given'
-      errorInvalidScheduleTime:   'Given scheduling time is not valid'
+      errorInvalidScheduleTime:   "Given scheduling time '$time$' is not valid"
       errorNoActionSpecified:     'No action (star, marking unread, move to inbox, etc.) specified'
       errorStoringFailed:         'Storing the scheduling action failed'

@@ -60,7 +69,7 @@
       @addButlerMail params
       true
     catch e
-      ErrorCodes.toReadable e
+      e.getLocalisedMessage()

   @uninstall: (automatic) ->
     if not automatic and (base = ScriptApp.getService().getUrl())
@@ -95,8 +104,6 @@
     if ScriptApp.getScriptTriggers().length is 0
       Logger.log "Installing trigger"
       ScriptApp.newTrigger("process").timeBased().everyMinutes(MailButler.FREQUENCY_MINUTES).create()
-
-

     # Get a lock for the current user
     lock = LockService.getPrivateLock()
@@ -240,7 +247,9 @@

     if not messageId or not (message = GmailApp.getMessageById messageId)
       Logger.log "Given message ID '%s' is not valid", messageId
-      throw ErrorCodes.INVALID_MESSAGE_ID
+      throw new Error ErrorCodes.INVALID_MESSAGE_ID,
+        email:      encodeURIComponent @getEmail()
+        messageId:  messageId

     now = new Date().getTime()
     matches = form.when?.match /^(delta|specified):([0-9]+)/
@@ -250,10 +259,10 @@
       else
         unless form.when
           Logger.log 'No scheduling time given'
-          throw ErrorCodes.NO_SCHEDULE_TIME
+          throw new Error ErrorCodes.NO_SCHEDULE_TIME
         else
           Logger.log "Given scheduling time '%s' is not valid", form.when
-          throw ErrorCodes.INVALID_SCHEDULE_TIME
+          throw new Error ErrorCodes.INVALID_SCHEDULE_TIME {time: form.when}

     props =
       messageId: messageId
@@ -271,11 +280,11 @@

     unless props.how.star or props.how.unread or props.how.inbox
       Logger.log "No action specified"
-      throw ErrorCodes.NO_ACTION
+      throw new Error ErrorCodes.NO_ACTION

     stored = MailButler.storeButlerMail props
-    throw ErrorCodes.STORE_FAILED unless stored
+    throw new Error ErrorCodes.STORE_FAILED unless stored

     thread = GmailApp.getThreadById message.getThread().getId()
     thread.moveToArchive() if String(form.archive) is "true"
@@ -306,8 +315,8 @@
       out = butler.scheduleJson request.parameter
     when 'setup'
       status = butler.scheduleSetup request.parameter
-      out = ContentService.createTextOutput i18n.get 'setupComplete'
-      out.append "\n"
+      out = HtmlService.createHtmlOutput i18n.get 'setupComplete'
+      out.append "
"
       if status is true
         out.append i18n.get 'scheduleSuccessCloseWindow'
       else


remote test code

remote test code

--- lib/CSON/cson.coffee
+++ lib/CSON/cson.coffee
@@ -11,17 +11,4 @@
 # util   = require 'util'

 exports.parse  = parse  = (cson) -> return vm.runInThisContext(coffee.compile(cson, {bare: yes}))
-exports.toJSON = toJSON = (cson, uglify = no) -> JSON.stringify(parse(cson), null, if uglify then null else '  ')
-
-# a = '''
-# test:
-#   node_1: [1,2,3]
-#   node_2: "string-value"
-#   node_3: 'string'
-#   node_4: [1,"2", yes, on]
-#   node_5: off
-#   node_6:
-#     sub_node_2: 1234
-# '''
-#
-# console.log parse(a)+exports.toJSON = toJSON = (cson, uglify = no) -> JSON.stringify(parse(cson), null, if uglify then null else '  ')


Update select list to work with atom-space-pen-views SelectList

--- lib/snippets-available.coffee
+++ lib/snippets-available.coffee
@@ -25,7 +25,7 @@
       @populate()
       @attach()

-  detach: ->
+  cancelled: ->
     @editor = null
     if @panel?
       @panel.destroy()


:fire: old dev-mode command no longer used

--- lib/developer.coffee
+++ lib/developer.coffee
@@ -18,17 +18,6 @@

     commands =
       'toggle-debug': => @toggleDebug()
-
-      # TODO remove once finished #158
-      'debug-highlight-search': ->
-        globalState = require './global-state'
-        editor = atom.workspace.getActiveTextEditor()
-        vimState = getEditorState(editor)
-        console.log 'highlightSearchPattern', globalState.highlightSearchPattern
-        console.log "vimState's id is #{vimState.id}"
-        console.log "hlmarkers are"
-        vimState.highlightSearchMarkers.forEach (marker) ->
-          console.log marker.getBufferRange().toString()

       'open-in-vim': => @openInVim()
       'generate-introspection-report': => @generateIntrospectionReport()


[Completes #35714361] precious stream instead of child_process object

--- lib/ephemeris.coffee
+++ lib/ephemeris.coffee
@@ -168,9 +168,9 @@
     # Massage can handle.  The `eden` (cli) sets up an `Array`
     # if `--out` is a comma-delimited sequence.
     if _.isArray @settings.out
-      streamin = @pre ephemeris.stdout
+      streamin = @pre ephemeris
       massage = new Massage @settings.out
-      massage.pipe streamin, stream, "utf8"
+      massage.pipe streamin, stream

     # The rest of these are special cases or else straight output of whatever
     # precious returns.
@@ -180,26 +180,24 @@
     # single massage steps for a more readable json output.
     else if _.include ["inspect", "indent"], @settings.out
       massage = new Massage ["json", @settings.out]
-      massage.pipe ephemeris.stdout, stream, "utf8"
+      massage.pipe ephemeris, stream

     # The most readable output of `eden` and
     # the default in the context of cli usage.
     else if @settings.out is "phase"
-      phase (@pre ephemeris.stdout), stream
+      phase (@pre ephemeris), stream

     # Unprocessed - straight from *precious*, whatever didn't get caught above.
     # For example `eden -o pprint`.
     # Unless @pre modifies the data (points).
-    else @pre(ephemeris.stdout).pipe stream
+    else @pre(ephemeris).pipe stream

     # Special (error) cases.
-    ephemeris.stderr.on "data", (data) -> console.log data.toString("ascii")
-    ephemeris.on "exit", (code) ->
-      if code isnt 0 then console.log 'ephemeris exited with code ' + code;
+    ephemeris.on "error", (data) -> console.log data.toString()

     # Know when all the data has been got.
     # Useful for post-formatting with trailing `\n` by the cli, for example.
-    ephemeris.stdout.on "end", -> stream.emit "end"
+    ephemeris.on "end", -> stream.emit "end"

 module.exports = Ephemeris


Better ping calls.

Better ping calls.

--- packages/crypto/client.coffee
+++ packages/crypto/client.coffee
@@ -186,7 +186,7 @@
       return

     if @current.message is 'ping'
-      @ping()
+      @ping @current.data
      return

     assert.equal @current.message, 'update'
@@ -215,7 +215,7 @@
   finalize: (params) ->
     throw new Error "Not implemented!"

-  ping: ->
+  ping: (data) ->
     throw new Error "Not implemented!"

   destroy: ->
@@ -250,10 +250,10 @@
     @worker.postMessage
       message: 'finalize'

-  ping: =>
+  ping: (data) =>
     @worker.postMessage
       message: 'ping'
-      data: @current.data
+      data: data

   destroy: =>
     @worker.terminate()


client import style

client import style

--- lib/runtime/completions.coffee
+++ lib/runtime/completions.coffee
@@ -1,12 +1,12 @@
 evaluation = require './evaluation'
 {client} =   require '../connection'
+
+{completions} = client.import 'completions'

 module.exports =
   selector: '.source.julia'
   filterSuggestions: true
   excludeLowerPriority: false
-
-  client: client.import 'completions'

   completionsData: (ed, pos) ->
     module: ed.juliaModule
@@ -15,7 +15,7 @@
     path: ed.getPath()

   getCompletions: (ed, pos) ->
-    @client.completions @completionsData(ed, pos)
+    completions @completionsData(ed, pos)

   toCompletion: (c) ->
     if c.constructor == String


fix completions for unicode prefixes

fix completions for unicode prefixes

--- lib/runtime/completions.coffee
+++ lib/runtime/completions.coffee
@@ -29,7 +29,7 @@
     completions
       path: editor.getPath()
       mod: modules.current()
-      line: editor.getTextInBufferRange [[row, 0], [row, Infinity]]
+      line: editor.getTextInBufferRange [[row, 0], [row, column]]
       column: column+1
       force: activatedManually || false


Added windowScroll function for ELEM

Added windowScroll function for ELEM

--- js/core/elem/elem.coffee
+++ js/core/elem/elem.coffee
@@ -801,6 +801,15 @@
         @setHTML( _id, _options.html )
     @_elements[_targetId].appendChild(_elem)
     _id
+
+  ###
+  Returns window scroll position as [ left, top ]
+  ###
+  windowScroll: ->
+    [
+      window.pageXOffset || document.documentElement.scrollLeft
+      window.pageYOffset || document.documentElement.scrollTop
+    ]

   ###
   Returns inner size of the browser window as [ width, height ]


cleanup

cleanup

--- lib/js2coffee.coffee
+++ lib/js2coffee.coffee
@@ -516,7 +516,7 @@
     else
       c.add 'catch'

-    c.scope @body(n.block),1,n.block
+    c.scope @body(n.block)
     @l(n)+c

   # `?` (ternary operator)
@@ -563,8 +563,8 @@
     if isSingleLine(body_) and statement isnt "loop"
       c.add "#{trim body_}  #{statement}\n"
     else
-      c.add statement,n
-      c.scope body_,n.body
+      c.add statement
+      c.scope body_
     @l(n)+c

   'do': (n) ->
@@ -699,7 +699,6 @@
     @l(n)+c

   'var': (n) ->
-    # TODO: add correct source line numbers instead of n.lineno for all
     list = _.map n.children, (item) =>
       "#{unreserve item.value} = #{if item.initializer? then @build(item.initializer) else 'undefined'}"

@@ -713,11 +712,11 @@
   #  * Break labels (`my_label: ...`)
   #  * Constants

-  'other': (n) ->   @l(n)+@unsupported n, "#{n.typeName()} is not supported yet"
-  'getter': (n) ->  @l(n)+@unsupported n, "getter syntax is not supported; use __defineGetter__"
-  'setter': (n) ->  @l(n)+@unsupported n, "setter syntax is not supported; use __defineSetter__"
-  'label': (n) ->   @l(n)+@unsupported n, "labels are not supported by CoffeeScript"
-  'const': (n) ->   @l(n)+@unsupported n, "consts are not supported by CoffeeScript"
+  'other': (n) ->   @unsupported n, "#{n.typeName()} is not supported yet"
+  'getter': (n) ->  @unsupported n, "getter syntax is not supported; use __defineGetter__"
+  'setter': (n) ->  @unsupported n, "setter syntax is not supported; use __defineSetter__"
+  'label': (n) ->   @unsupported n, "labels are not supported by CoffeeScript"
+  'const': (n) ->   @unsupported n, "consts are not supported by CoffeeScript"

   'block': (args...) ->
     @script.apply @, args


some more surgeries on the ancient codes

--- library/nucleus/routing.coffee
+++ library/nucleus/routing.coffee
@@ -43,6 +43,7 @@
     # the kernel instance as a parameter. You can override it as
     # you see fit, but be sure to invoke the super constructor and
     # it is highly advised to store the kernel instance in object.
+    # It is also taking care of assigning a service identification.
     constructor: (@kernel) -> super

     # The method implements a middleware (for Connect) that looks


Added test that asserts error throwing + minor code style fixes

--- test.coffee
+++ test.coffee
@@ -1,6 +1,5 @@
 test = require "ava"
 {IncomingMessage} = require "http"
-Stream =  require "stream"
 {Socket} = require "net"

 busboy = require "."
@@ -10,12 +9,12 @@
   await return

 test "Should return a promise", (t) ->
-  req = request()
-  bb = busboy(req)
-  t.is bb instanceof Promise, true
+  t.is busboy(request()) instanceof Promise, true
+
+test "Should throw an error if request parameter isn't an instance of http.IncomingMessage", (t)->
+  t.throws(busboy({}), "Request parameter must be an instance of http.IncomingMessage.")

 request = ()->
-
   req = new IncomingMessage new Socket({readeble: true})
   req.headers =
     'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryoyYHBQpNew47X0cp'


change sign in screen when get_apikey is set.

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -101,10 +101,6 @@
         style           : rtypes.object
         has_remember_me : rtypes.bool

-    reduxProps:
-        page:
-            get_apikey : rtypes.string
-
     make_account: (e) ->
         e.preventDefault()
         first_name = ReactDOM.findDOMNode(@refs.first_name).value
@@ -142,7 +138,6 @@
             well_class = 'webapp-landing-sign-up-highlight'
         <Well style=well_style className={well_class}>
             <TermsOfService style={fontWeight:'bold', textAlign: "center"} />
-            {
Get api key
 if @props.get_apikey}

             {@display_token_input()}
             {@display_error("token")}
@@ -584,12 +579,33 @@
         has_remember_me         : rtypes.bool
         has_account             : rtypes.bool

-    render: ->
-        if @props.remember_me
+    reduxProps:
+        page:
+            get_apikey : rtypes.string
+
+    render_password_reset: ->
+        reset_key = reset_password_key()
+        if not reset_key
+            return
+        <ResetPassword
+            reset_key            = {reset_key}
+            reset_password_error = {@props.reset_password_error}
+        />
+
+    render_forgot_password: ->
+        if not @props.show_forgot_password
+            return
+        <ForgotPassword
+            forgot_password_error   = {@props.forgot_password_error}
+            forgot_password_success = {@props.forgot_password_success}
+        />
+
+    render_main_page: ->
+        if @props.remember_me and not @props.get_apikey
+            # Just assume user will be signing in.
             # CSS of this looks like crap for a moment; worse than nothing. So disabling unless it can be fixed!!
             #return <Connecting />
             return
-        reset_key = reset_password_key()
         if @props.has_remember_me
             topbar =
               img_icon    : APP_ICON_WHITE
@@ -608,14 +624,8 @@
               border      : "5px solid #{COLORS.GRAY}"

-                {<ResetPassword
-                    reset_key={reset_key}
-                    reset_password_error={@props.reset_password_error}
-                /> if reset_key}
-                {<ForgotPassword
-                    forgot_password_error={@props.forgot_password_error}
-                    forgot_password_success={@props.forgot_password_success}
-                /> if @props.show_forgot_password}
+            {@render_password_reset()}
+            {@render_forgot_password()}
             <Row style={fontSize: UNIT,\
                         backgroundColor: COLORS.LANDING.LOGIN_BAR_BG,\
                         padding: 5, margin: 0, borderRadius:4}
@@ -707,10 +717,35 @@

-                        Learn more about CoCalc...
+                        {Learn more about CoCalc... if not @props.get_apikey}

                 </Col>
             </Row>

+    render: ->
+        main_page = @render_main_page()
+        if not @props.get_apikey
+            return main_page
+        app = misc.capitalize(@props.get_apikey)
+

+

+

+                    CoCalc API Key Access
+

+

+                    {app} would like your CoCalc API key.
+

+

+                    This grants full access to all of your CoCalc projects to {app}, until you explicitly revoke your API key in Account preferences.
+

+

+                    Please sign in or create an account below.
+

+

+

+            {main_page}
+

+
+


Fetch staged projection

Fetch staged projection

--- src/spec/integration.spec.coffee
+++ src/spec/integration.spec.coffee
@@ -193,7 +193,7 @@
       .then (result) =>
         @logger.debug result, 'New retailer product created'
         # fetch the product again to get the correct version
-        @client.products.byId(result.body.id).fetch()
+        @client.productProjections.staged(true).byId(result.body.id).fetch()
       .then (result) =>
         @logger.debug result, 'Fetched product'
         data =
@@ -204,7 +204,7 @@
         @client.products.byId(result.body.id).update(data)
       .then (result) =>
         @logger.debug result, 'Retailer product published, fetching latest version of master product'
-        @client.products.byId(@masterProductId).fetch()
+        @client.productProjections.staged(true).byId(@masterProductId).fetch()
       .then (result) =>
         data =
           actions: [


Release keystore file path can lead to unexpected escaped characters (.coffee)

--- src/tasks/release/android.coffee
+++ src/tasks/release/android.coffee
@@ -27,6 +27,7 @@
     keyStorePath = @path.relative @_platformPath('android'), @config.key.store
     properties = []
     properties.push "key.store=#{keyStorePath}"
+    properties.push "key.store=#{if @path.sep == '\\' then keyStorePath.replace /\\/g, '\\\\' else keyStorePath}" # Path must be escaped in the file as well
     properties.push "key.alias=#{@config.key.alias}"

     if includePasswords