Add more code that almost works

Add more code that almost works

--- static/scripts/main.coffee
+++ static/scripts/main.coffee
@@ -135,138 +135,120 @@
             keysPressed[event.key] = false
             @setState keysPressed: keysPressed

-ChatConnected = React.createClass
-    displayName: "ChatConnected"
+Chat = React.createClass
+    displayName: "Foo"

     getInitialState: ->
         messages: []

-    componentDidMount: ->
-        _.forEach @props.connections, (connection) =>
-            connection.on "data", (blob) =>
-                if blob.type == "message"
-                    @addMessage blob.data
+    connectionsToPeerIds: (connections) ->
+        _.map connections, (connection) => connection.peer
+
+    componentWillMount: ->
+        who = getSegments()[1]
+        console.log "who: #{ who }"
+
+        peer = new Peer who, key: PEER_KEY, debug: 2
+        console.log "new Peer()"
+
+        @connections = []
+
+        peer.on "error", (error) =>
+            alert error.type
+
+        peer.on "open", (peerId) =>
+            console.log "peer.on"
+            console.log "peerId: #{ peerId }"
+
+            if who == "x"
+                console.log "acting as #{ who }, host"
+
+                peer.on "connection", (connection) =>
+                    console.log "new connection"
+                    console.log "peer #{ connection.peer } just connected to host #{ peerId }"
+
+                    @connections.push connection
+
+                    peerIds = @connectionsToPeerIds @connections
+
+                    console.log "connected peers: #{ peerIds }"
+
+                    _.forEach @connections, (c) =>
+                        c.on "open", =>
+                            console.log "sending connections to #{ c.peer }"
+
+                            c.send type: "newConnection", peerIds: peerIds
+
+                    connection.on "data", (data) =>
+                        if data.type == "message"
+                            messages = @state.messages
+                            messages.push data.message
+                            @setState messages: messages
+
+            if who != "x"
+                console.log "acting as #{ who }, normal peer"
+
+                connection = peer.connect "x"
+                console.log "connecting to x, host"
+
+                @connections.push connection
+
+                connection.on "error", (error) =>
+                    alert error
+
+                connection.on "open", =>
+                    console.log "connection to #{ connection.peer } open"
+
+                    connection.on "data", (data) =>
+                        if data.type == "message"
+                            messages = @state.messages
+                            messages.push data.message
+                            @setState messages: messages
+
+                    connection.on "data", (data) =>
+                        if data.type == "newConnection"
+                            peerIds = data.peerIds
+                            peerIds = _.filter peerIds, (peerId) -> peerId != who
+
+                            console.log "new connections without my connection: #{ peerIds }"
+
+                            _.forEach peerIds, (peerId) =>
+                                console.log "connecting to #{ peerId }"
+
+                                connection = peer.connect peerId
+
+                                connection.on "error", (error) =>
+                                    alert error.type
+
+                                connection.on "open", =>
+                                    console.log "connection to #{ connection.peer } open"
+
+                                    @connections.push connection
+
+                                    connection.on "data", (data) =>
+                                        console.log "RECEIVED MESSAGE FROM NEW PEER"
+                                        if data.type == "message"
+                                            messages = @state.messages
+                                            messages.push data.message
+                                            @setState messages: messages

     render: ->
         (Dom.div null,
             (ChatMessages messages: @state.messages),
             (ChatForm sendMessage: @sendMessage))

-    addMessage: (message) ->
+    sendMessage: (message) ->
         messages = @state.messages
         messages.push message
         @setState messages: messages

-    sendMessage: (message) ->
-        @addMessage message
-
-        _.forEach @props.connections, (connection) ->
-            blob =
-                type: "message"
-                data: message
-            connection.send blob
-
-
-ChatDisconnected = React.createClass
-    mixins: [React.addons.LinkedStateMixin]
-
-    displayName: "ChatDisconnected"
-
-    getInitialState: ->
-        otherId: ""
-
-    render: ->
-        chatLink = "#{ location.origin + location.pathname }#/chat/#{ @props.peerId }"
-
-        (Dom.div null,
-            (Dom.form null,
-                (Input id: "link", label: "Chat Link", readOnly: true, value: chatLink)
-            ))
-
-Chat = React.createClass
-    displayName: "Chat"
-
-    getInitialState: ->
-        currentComponent: Noop()
-
-    componentWillMount: ->
-        @peer = new Peer key: PEER_KEY, debug: 2
-        @connections = []
-
-        otherPersonPeerId = getSegments()[1]
-
-        @peer.on "open", (peerId) =>
-            @peerId = peerId
-
-            if not otherPersonPeerId?
-                @setState currentComponent: ChatDisconnected(peerId: @peerId)
-
-                @peer.on "connection", (connection) =>
-                    connection.on "error", (error) ->
-                        console.log error
-
-                    @connections.push connection
-
-                    connection.on "open", =>
-                        _.forEach @connections, (connection) =>
-                            blob =
-                                type: "connections"
-                                data: _.map @connections, (connection) -> connection.peer
-                            connection.send blob
-
-                        @setState currentComponent: ChatConnected(peerId: @peerId, connections: @connections)
-            else
-                connection = @peer.connect otherPersonPeerId
-                connection.on "error", (error) ->
-                    console.log error
-
-                @connections.push connection
-
-                window.original_connection = connection
-
-                connection.on "open", =>
-
-                    connection.on "data", (blob) =>
-                        if blob.type == "connections"
-                            console.log @peerId
-                            peerIds = blob.data
-                            console.log peerIds
-                            peerIds = _.filter peerIds, (peerId) => peerId != @peerId
-                            console.log peerIds
-
-                            _.forEach peerIds, (peerId) =>
-                                connection = @peer.connect peerId
-                                connection.on "error", (error) ->
-                                    console.log error
-
-                                @connections.push connection
-
-                            @setState currentComponent: ChatConnected(peerId: @peerId, connections: @connections)
-
-    componentDidMount: ->
-        # TODO: This should be in `componentWillMount`.
-        @peer.on "error", =>
-            @props.addAlert type: "danger",
-                (Dom.span null,
-                    (Dom.strong null, "Oh snap! "),
-                    (Dom.span null, "Something went terribly wrong with WebRTC."))
-
-    render: ->
-        @state.currentComponent
-
-    onConnect: (connection) ->
-        @props.addAlert type: "warning",
-            (Dom.span null,
-                (Dom.strong null, "Well done! "),
-                (Dom.span null, "You are connected now."))
-
-    onClose: (connection) ->
-        connection.on "close", =>
-            @props.addAlert type: "warning",
-                (Dom.span null,
-                    (Dom.strong null, "Warning! "),
-                    (Dom.span null, "The other person just disconnected."))
-            @setState currentComponent: ChatDisconnected(peerId: @peerId)
+        data =
+            type: "message"
+            message: message
+
+        _.forEach @connections, (c) ->
+            console.log "sending message to #{ c.peer }"
+            c.send data

 Settings = React.createClass
     mixins: [React.addons.LinkedStateMixin]
@@ -294,111 +276,9 @@

         @props.addAlert type: "success", "Changes saved!"

-Foo = React.createClass
-    displayName: "Foo"
-
-    getInitialState: ->
-        log: []
-
-    connectionsToPeerIds: (connections) ->
-        _.map connections, (connection) => connection.peer
-
-    componentWillMount: ->
-        who = getSegments()[1]
-        @log "who: #{ who }"
-
-        peer = new Peer who, key: PEER_KEY, debug: 2
-        @log "new Peer()"
-
-        connections = []
-
-        peer.on "error", (error) =>
-            alert error.type
-
-        peer.on "open", (peerId) =>
-            @log "peer.on"
-            @log "peerId: #{ peerId }"
-
-            if who == "x"
-                @log "acting as #{ who }, host"
-
-                peer.on "connection", (connection) =>
-                    @log "new connection"
-                    @log "peer #{ connection.peer } just connected to host #{ peerId }"
-
-                    connections.push connection
-
-                    peerIds = @connectionsToPeerIds connections
-
-                    @log "connected peers: #{ peerIds }"
-
-                    _.forEach connections, (c) =>
-                        c.on "open", =>
-                            @log "sending connections to #{ c.peer }"
-
-                            c.send type: "newConnection", peerIds: peerIds
-
-                    connection.on "data", (data) =>
-                        if data.type == "message"
-                            @log "MESSAGE: #{ data.message }"
-
-            if who != "x"
-                @log "acting as #{ who }, normal peer"
-
-                connection = peer.connect "x"
-                @log "connecting to x, host"
-
-                connection.on "error", (error) =>
-                    alert error
-
-                connection.on "open", =>
-                    @log "connection to #{ connection.peer } open"
-
-                    connection.on "data", (data) =>
-                        if data.type == "newConnection"
-                            peerIds = data.peerIds
-                            peerIds = _.filter peerIds, (peerId) -> peerId != who
-
-                            @log "new connections without my connection: #{ peerIds }"
-
-                            _.forEach peerIds, (peerId) =>
-                                @log "connecting to #{ peerId }"
-
-                                connection = peer.connect peerId
-
-                                connection.on "error", (error) =>
-                                    alert error.type
-
-                                connection.on "open", =>
-                                    @log "connection to #{ connection.peer } open"
-
-                                    connections.push connection
-
-        window.send = (message) =>
-            console.log connections
-            @sendMessage connections, message
-
-    render: ->
-        (Dom.pre null, _.map @state.log, (x) -> "#{ x }\n")
-
-    log: (what) ->
-        log = @state.log
-        log.push what
-        @setState log: log
-
-    sendMessage: (connections, message) ->
-        data =
-            type: "message"
-            message: message
-
-        _.forEach connections, (c) ->
-            c.send data
-
 routes = [
-    ["/chat", Chat]
-    [/chat\/[a-z0-9]+/, Chat]
+    [/chat\/\w+/, Chat]
     ["/settings", Settings]
-    [/foo\/\w+/, Foo]
 ]

 navbarItems = [


broadcastTimeout fixed

broadcastTimeout fixed

--- src/public/models/node.coffee
+++ src/public/models/node.coffee
@@ -479,11 +479,9 @@
 		#
 		_onTokenInfo: ( peer, tokenString, vectorString, instantiate = true ) =>
 			token = Token.deserialize(tokenString)
-			console.log "Received info about ",  token
 			token.coordinates = Vector.deserialize(vectorString)
 			@addToken(token)
 			if @token? and instantiate
-				console.log "Ik heb een token ", @token
 				@emitTo(token.nodeId, 'token.info', @token.serialize(), @coordinates.serialize(), false)

 		# Adds a token to the collection of foreign tokens
@@ -522,7 +520,7 @@
 				@broadcast('token.requestCandidate', @token.serialize())
 				setTimeout( ( ) =>
 					@_pickNewTokenOwner()
-				, broadcastTimeout)
+				, @broadcastTimeout)
 			else
 				@setSuperNode(true)


make folder ids unique

make folder ids unique

--- brand-index/PermissionsService.coffee
+++ brand-index/PermissionsService.coffee
@@ -12,7 +12,7 @@
 			.map(_.partialRight(_.pick, ["group_id", "pathFolderIDs"]))
 			.groupBy("group_id")
 			.mapValues (values)=>
-				_.flatten(_.pluck(values, "pathFolderIDs"))
+				_.uniq(_.flatten(_.pluck(values, "pathFolderIDs")))
 			.value()

 		console.log(@permissionsIndex)


Fix: a test relied on faulty behavior (#5028)

`assert.throws` did not test for the error message so far. This
changes it to actually test for the error message.

--- test/exception_handling.coffee
+++ test/exception_handling.coffee
@@ -8,7 +8,7 @@
 # Throw

 test "basic exception throwing", ->
-  throws (-> throw 'error'), 'error'
+  throws (-> throw 'error'), /^error$/

 # Empty Try/Catch/Finally


allow both promises and symbolTables as input also light refactoring

--- lib/gotodef.coffee
+++ lib/gotodef.coffee
@@ -3,34 +3,38 @@

 # ## GoToDef-Panel
 #
-# `goto` takes a promise as its argument, which can either be fulfilled or rejected.
-# Both modes will be handled by `goto` as follows:
-# On fulfillment, a `result` object is returned which contains two fields:
+# `goto` either takes a `symbolTable` as its argument, or a Promise which returns a
+# `symbolTable`.
 #
-#   - `result.error`: Boolean. If true, the contents of `result.items` will be
-#   shown as an error.
+# A `symbolTable` is specified by having the following fields:
 #
-#   - `result.items` -  Array that contains objects with the fields
+#   - `symbolTable.error`: Boolean. If true, the contents of `result.items` will
+#   be shown as an error.
+#
+#   - `symbolTable.items` -  Array that contains objects with the fields
 #     - `.text` -       Displayed text, searchable.
 #     - `.file` -       File in which this method is defined, not displayed.
 #     - `.line` -       Line of definition.
 #     - `.dispfile` -   Humanized file path, displayed.
 #
-#   or a plain text string if `result.error` is true.
+#   or a plain text string if `symbolTable.error` is true.

 module.exports =
-goto: (promise) ->
+goto: (symbolTable) ->
   @view ?= new GotoView()

-  promise.then (result) =>
-    if result.error
-      @view.setError result.items
+  # this allows either a promise or a result as the input
+  promise = Promise.resolve symbolTable
+
+  promise.then (symbolTable) =>
+    if symbolTable.error
+      @view.setError symbolTable.items
       @view.show()
-    else if result.items.length == 1
-      GotoView.openItem result.items[0]
-    else if result.items.length > 1
-      @view.setItems result.items
+    else if symbolTable.items.length == 1
+      GotoView.openItem symbolTable.items[0]
+    else if symbolTable.items.length > 1
+      @view.setItems symbolTable.items
       @view.show()

 class GotoView extends SelectListView


add TODO for loop use rewrite

add TODO for loop use rewrite

--- lib/chain.coffee
+++ lib/chain.coffee
@@ -248,6 +248,8 @@

       when 'string' # it's an id of a function, so find it

+        # TODO:
+        # return i for fn, i in @array when which is fn?.options?.id
         for fn, i in @array
           if which is fn?.options?.id then return i


Rename variable to prevent a TypeError

Rename variable to prevent a TypeError

--- lib/check.coffee
+++ lib/check.coffee
@@ -3,7 +3,6 @@
 github_graphql = require 'github-graphql-client'
 semver = require 'semver'
 query = require './query'
-

 ###
   Executes a GraphQL query on the API to fetch either the latest release or tag.
@@ -16,14 +15,14 @@
 ###
 graphql = (options, callback) ->
   # build the query
-  query =
+  theQuery =
     if options.fetchTags
       query.tags options.repo, options.owner
     else
       query.releases options.repo, options.owner

   # do the api call
-  github_graphql (token: options.token, query: query), (err, res) ->
+  github_graphql (token: options.token, query: theQuery), (err, res) ->
     if err
       callback err, null
     else


Fixed bug in hash trie code (indexed.coffee)

--- lib/indexed.coffee
+++ lib/indexed.coffee
@@ -216,7 +216,7 @@
     if node?
       new ArrayNode(@progeny, i, node, @size - 1)
     else
-      remaining = (j for j in [1...@progeny.length] when j != i and @progeny[j])
+      remaining = (j for j in [0...@progeny.length] when j != i and @progeny[j])
       if remaining.length <= 4
         bitmap = Sequence.reduce(remaining, 0, (b, j) -> b | (1 << j))
         array  = (@progeny[j] for j in remaining)


runtime: Send component:componentsready on component:list

Was part of the spec since around 12 months.
Required for topic fixture support in fbp-spec

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -80,7 +80,8 @@

 listComponents = (runtime, connection) ->
     componentLib = runtime.library
-    for name of componentLib.getComponents()
+    components = componentLib.getComponents()
+    for name of components
         comp = componentLib.getComponent(name)
         # TODO: also allow to send icon definitions
         resp =
@@ -93,7 +94,11 @@
                 outPorts: portDefAsArray(componentLib.outputPortsFor(name))

         connection.send resp
-    return
+
+    connection.send
+        protocol: 'component'
+        command: 'componentsready'
+        payload: Object.keys(components).length

 sendExportedPorts = (connection, runtime) ->
     # go over runtime.graph and expose exported ports


clean debug cod

clean debug cod

--- lib/storage.coffee
+++ lib/storage.coffee
@@ -23,7 +23,6 @@
       vals: {}
       links: {}
     @client.del [@redisValuesKey, @redisLinksKey], ->
-      console.log "0000000"
       cb()

   loadData: ->


fix server integration test

fix server integration test

--- test/integration/server.coffee
+++ test/integration/server.coffee
@@ -25,7 +25,6 @@
     describe 'API', ->
       api = null
       beforeEach (done)->
-        Server._names()
         Server.start module, '../data/index', ->
           done()


fix newrelic.coffee

fix newrelic.coffee

The Newrelic script didn't work (at least on our new hubot). Afaict this was happening because of variables being referenced out of scope.

This fixed it for us.

--- src/scripts/newrelic.coffee
+++ src/scripts/newrelic.coffee
@@ -22,8 +22,10 @@
 # Author:
 #   briandoll

+Parser = require("xml2js").Parser
+
 module.exports = (robot) ->
-  fetchData = (appId) ->
+  fetchData = (accountId, appId, apiKey, msg) ->
     msg.http("https://rpm.newrelic.com/accounts/#{accountId}/applications/#{appId}/threshold_values?api_key=#{apiKey}")
       .get() (err, res, body) ->
         if err
@@ -41,6 +43,5 @@
     accountId = process.env.HUBOT_NEWRELIC_ACCOUNT_ID
     appIds     = process.env.HUBOT_NEWRELIC_APP_ID.split(',')
     apiKey    = process.env.HUBOT_NEWRELIC_API_KEY
-    Parser = require("xml2js").Parser
-    fetchData(appId) for appId in appIds
+    fetchData(accountId, appId, apiKey, msg) for appId in appIds


accept either 1 or 2 args.  if 2 args, it's (level_required, msg).  if 1 arg, it's (msg)

--- lib/debug.coffee
+++ lib/debug.coffee
@@ -2,12 +2,22 @@
 indent = 0
 debug_level = 0

-exports.log = (msg, level = 1) ->
+exports.log = () ->
+        level = 3
+        msg = null
+
+        if arguments.length > 1
+                level = arguments[0]
+                msg = arguments[1]
+        else if arguments.length == 1
+                msg = arguments[0]
+
         if debug_level < level
                 return
         if typeof msg is "function"
                 msg = msg()
-        console.warn "#{(' ' for i in [0..indent]).join('')}#{msg}"
+        if msg?
+                console.warn "#{(' ' for i in [0..indent]).join('')}#{msg}"

 exports.indent = () -> indent += 1
 exports.unindent = () -> indent -= 1


Add comment on cleanup

Add comment on cleanup

--- lib/index.coffee
+++ lib/index.coffee
@@ -15,7 +15,6 @@
 # - Page Footer  -> document.getElementById('pageFooter')
 #
 # When no #pageContent is available, phantomjs will use document.body as pdf content
-
 script = path.join(__dirname, 'scripts/pdf_a4_portrait.coffee')

 exports.create = (string, options, callback) ->
@@ -43,6 +42,7 @@
     child.kill()

   child.on 'exit', (code) ->
+    # Clean up the timeout cause the process ended anyways
     clearTimeout(timeout)
     if (stderr.length || code) > 0
       error = new Error(Buffer.concat(stderr).toString())


Add Workspace::getActivePaneItem

Add Workspace::getActivePaneItem

--- lib/index.coffee
+++ lib/index.coffee
@@ -39,5 +39,5 @@
       openPanel('Packages')

     atom.workspaceView.on 'pane-container:active-pane-item-changed', ->
-      if settingsView is atom.workspaceView.getActivePaneItem()
+      if settingsView is atom.workspace.getActivePaneItem()
         settingsView?.redrawEditors()


Introduce 'persistent' option

Introduce 'persistent' option

Using `persistent="true"` in your HTML dropdown template won't hide
the dropdown when clicking anywhere in the document.

Signed-off-by: Johan Brook <786a2407a4f0c132b7d253eb7000b7a8b134a366@gmail.com>

--- dropdown.coffee
+++ dropdown.coffee
@@ -15,6 +15,7 @@
 Dropdown = (opts = {}) ->
   defaults =
     showing: false
+    persistent: false
     direction: Direction.SOUTH
     align: 'center'
     x: 0
@@ -32,6 +33,9 @@
       for prop of opts
         if Match.test prop, Match.OneOf('x', 'y', 'top', 'left')
           obj[prop] = toIntOr opts[prop], obj[prop]
+        else if prop is 'persistent'
+          # Convert to real bool
+          obj[prop] = opts[prop] is 'true'
         else
           if opts[prop] and _.isUndefined(opts[prop]) is false
             obj[prop] = opts[prop]
@@ -48,8 +52,11 @@
 DropdownsStruct = ->
   list = new ReactiveDict()

-  except = (key) ->
-    _.without(Object.keys(list.keys), key)
+  allKeys = ->
+    Object.keys(list.keys)
+
+  except = (keys) ->
+    _.without(allKeys(), keys...)

   get = (key) ->
     list.get(key)
@@ -87,7 +94,7 @@
       list.set(key, dropdown)

   hideAll = (keys) ->
-    keys = keys or Object.keys(list.keys)
+    keys = keys or allKeys()
     keys.forEach(hide)

   isShown = (key) ->
@@ -108,7 +115,7 @@
     list.set(key, null)

   removeAll = ->
-    Object.keys(list.keys).forEach(remove)
+    allKeys().forEach(remove)

   create: add
   get: get
@@ -120,7 +127,10 @@
   remove: remove
   removeAll: removeAll
   setPosition: setPosition
-  hideAllBut: (key) -> hideAll except key
+  hideAllBut: (keys) ->
+    notTheseKeys = if Array.isArray(keys) then keys else [keys]
+    hideAll except notTheseKeys
+  getPersistentKeys: -> allKeys().filter( (key) -> list.get(key).persistent)

 # Singleton
 Dropdowns = DropdownsStruct()
@@ -137,8 +147,8 @@
 # Positioning

 center = (args) ->
-	middle = args[0] + args[1] / 2
-	middle - args[2] / 2
+  middle = args[0] + args[1] / 2
+  middle - args[2] / 2

 horizontally = ($el, $reference) ->
   [$reference.position().left, $reference.outerWidth(), $el.outerWidth()]
@@ -150,7 +160,7 @@

 Template.dropdown.created = ->
   # Build a dropdown from template attributes.
-  opts = _.pick(@data, 'align', 'top', 'left', 'direction')
+  opts = _.pick(@data, 'align', 'top', 'left', 'direction', 'persistent')
   Dropdowns.create(@data.name, opts)

 Template.dropdown.helpers
@@ -293,4 +303,6 @@
     # De Morgan's Laws, baby.
     #   (!P ^ !Q) => !(P v Q)
     if not (isDropdown(el) or isTrigger(el))
-      Dropdowns.hideAll()
+      # Hide all dropdowns that haven't declared that they wanna
+      # be persistent in document clicks.
+      Dropdowns.hideAllBut(Dropdowns.getPersistentKeys())


Add ssh-keys to URL parser

Add ssh-keys to URL parser

--- src/smc-webapp/history.coffee
+++ src/smc-webapp/history.coffee
@@ -110,6 +110,8 @@
                 redux.getActions('account').set_active_tab('upgrades')
             if segments[1] == 'support'
                 redux.getActions('account').set_active_tab('support')
+            if segments[1] == 'ssh-keys'
+                redux.getActions('account').set_active_tab('ssh-keys')
         when 'file-use'
             if not logged_in
                 return


filenames aren't necessarily random, e.g., timestamp or semantic... so call them something else

--- src/smc-webapp/r_account.cjsx
+++ src/smc-webapp/r_account.cjsx
@@ -1303,7 +1303,7 @@

     render_new_filenames: ->
         selected = @props.other_settings.get(NEW_FILENAMES) ? NewFilenames.default_family
-        <LabeledRow label='Random filenames'>
+        <LabeledRow label='Generated filenames'>
             <SelectorInput
                 selected  = {selected}
                 options   = {NewFilenameFamilies}


sync2: sage worksheets -- initial rendering needs to process

--- src/smc-webapp/syncdoc.coffee
+++ src/smc-webapp/syncdoc.coffee
@@ -1128,6 +1128,8 @@
                     @_last_remote_change = new Date()
                 update_unsaved_changes()

+            cb()
+
         synchronized_string
             project_id    : @project_id
             filename      : misc.meta_file(@filename, 'chat')


log return

log return

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -2,6 +2,7 @@

 	log = (args...) ->
     	console.log.apply console, args if console?.log and mb.debug is true
+    	return

 	class MailButler
 		debug: true


fix error notification

fix error notification

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -606,14 +606,11 @@
       (M.isAuthorisationErrorResponse status)
         @gettingStarted params ? {}
       else
-        notification =
+        chrome.extension.sendMessage
           action:   'notification'
-          icon:     "images/tie48x48.png"
+          icon:     "images/tie.svg"
           title:    __msg 'notificationScheduleErrorTitle'
           message:  if (_.isObject error) and ('code' of error) then (__msg "notificationScheduleError#{error.code}") else (__msg 'notificationScheduleError', '' + new String error)
-
-        chrome.extension.sendMessage notification
-
       return

   mb = new M


fix authorize detection

fix authorize detection

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -118,7 +118,7 @@
 			return

 		@isAuthorisationErrorPage: (contents) ->
-			/http?:\/\/www.google.com\/accounts\/OAuthAuthorizeToken/i.test contents
+			/https?:\/\/www\.google\.com\/accounts\/OAuthAuthorizeToken/i.test contents

 		checkAuthorised: (resp) ->
 			url = @getServiceURL()


fix a reference error

fix a reference error

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -37,7 +37,7 @@
     { name, correlationName, region } = options
     return (kite = @getCachedKite name, correlationName)?

-  fetchRegion: (vmName, region) ->
+  fetchRegion: (correlationName, region) ->
     if region
     then Promise.resolve region
     else Promise.reject new Error "TODO: implement vm region fetching"
@@ -59,7 +59,7 @@

     @setCachedKite name, correlationName, kite

-    @fetchRegion(vmName, region).then (region) =>
+    @fetchRegion(correlationName, region).then (region) =>

       @fetchKite
         query : { name, region }


Activity: public feed special case removed from open

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -54,7 +54,7 @@
     else @unsetClass 'fixed'

-  # type: [public|topic|post|message|chat|null]
+  # type: [topic|post|message|chat|null]
   # slug: [slug|id|name]
   open: (type, slug) ->

@@ -75,11 +75,7 @@
     @sidebar.selectItemByRouteOptions type, slug
     item = @sidebar.selectedItem

-    if type is 'public'
-      item = @sidebar.public
-      kallback item.getData()
-
-    else if not item
+    if not item
       type_ = switch type
         when 'message' then 'privatemessage'
         when 'post'    then 'activity'
@@ -154,7 +150,7 @@

   showNewMessageModal: ->

-    @open 'public'  unless @tabs.getActivePane()
+    @open 'topic', 'public'  unless @tabs.getActivePane()

     bounds = @sidebar.sections.messages.options.headerLink.getBounds()

@@ -176,13 +172,13 @@

   showAllTopicsModal: ->

-    @open 'public'  unless @tabs.getActivePane()
+    @open 'topic', 'public'  unless @tabs.getActivePane()

     return new YourTopicsModal delegate : this

   showAllConversationsModal: ->

-    @open 'public'  unless @tabs.getActivePane()
+    @open 'topic', 'public'  unless @tabs.getActivePane()

     return new ConversationsModal delegate : this


Activity: AppView refactored

Activity: AppView refactored

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -3,56 +3,136 @@

   headerHeight = 0

+  {entryPoint, permissions, roles} = KD.config
+
+  isGroup        = -> entryPoint?.type is 'group'
+  isKoding       = -> entryPoint?.slug is 'koding'
+  isMember       = -> 'member' in roles
+  canListMembers = -> 'list members' in permissions
+  isPrivateGroup = -> not isKoding() and isGroup()
+

   constructor:(options = {}, data)->

-    options.cssClass   = "content-page activity"
-    options.domId      = "content-page-activity"
+    options.cssClass   = 'content-page activity'
+    options.domId      = 'content-page-activity'

     super options, data

     # FIXME: disable live updates - SY
-    @appStorage = KD.getSingleton("appStorageController").storage 'Activity', '1.0.1'
+    {entryPoint}      = KD.config
+    {
+      windowController
+      widgetController
+      mainController
+      appStorageController
+    } = KD.singletons
+
+    @appStorage = appStorageController.storage 'Activity', '1.0.1'
     @appStorage.setValue 'liveUpdates', off

+    # main components
+    @feedWrapper       = new ActivityListContainer
+    @inputWidget       = new ActivityInputWidget
+    @referalBox        = new ReferalBox
+    @topWidgetWrapper  = new KDCustomHTMLView
+    @leftWidgetWrapper = new KDCustomHTMLView
+    @groupListBox      = new UserGroupList
+    @topicsBox         = new ActiveTopics
+    @usersBox          = new ActiveUsers

-  viewAppended:->
-
-    {entryPoint}      = KD.config
-    windowController  = KD.singleton 'windowController'
-
-    @feedWrapper      = new ActivityListContainer
-    @inputWidget      = new ActivityInputWidget
-
-    @inputWidget.on "ActivitySubmitted", =>
-      appTop   = @getElement().offsetTop
-      listTop  = @feedWrapper.listWrapper.getElement().offsetTop
-      duration = @feedWrapper.pinnedListWrapper.getHeight() * .3
-      $("html, body").animate {scrollTop: appTop + listTop + 10}, {duration}
-
-    @referalBox       = new ReferalBox
-    @groupListBox     = new UserGroupList
-    @topicsBox        = new ActiveTopics
-    @usersBox         = new ActiveUsers
-    # @tickerBox        = new ActivityTicker
     # TODO : if not on private group DO NOT create those ~EA
+    # group components
+    @groupCoverView   = new FeedCoverPhotoView
     @groupDescription = new GroupDescription
     @groupMembers     = new GroupMembers

-    @mainBlock        = new KDCustomHTMLView tagName : "main" #"activity-left-block"
-    @sideBlock        = new KDCustomHTMLView tagName : "aside"   #"activity-right-block"

-    @groupCoverView   = new FeedCoverPhotoView

-    @mainController   = KD.getSingleton("mainController")
-    @mainController.on "AccountChanged", @bound "decorate"
-    @mainController.on "JoinedGroup", => @inputWidget.show()
+    widgetController.showWidgets [
+      { view: @topWidgetPlaceholder,  key: 'ActivityTop'  }
+      { view: @leftWidgetPlaceholder, key: 'ActivityLeft' }
+    ]

+    @inputWidget.on 'ActivitySubmitted', @bound 'activitySubmitted'
+    mainController.on 'AccountChanged', @bound "decorate"
+    mainController.on 'JoinedGroup', => @inputWidget.show()
     @feedWrapper.ready =>
       @activityHeader  = @feedWrapper.controller.activityHeader
       {@filterWarning} = @feedWrapper
       {feedFilterNav}  = @activityHeader
       feedFilterNav.unsetClass 'multiple-choice on-off'
+
+    @once 'viewAppended', =>
+
+
+  viewAppended: ->
+
+    appendAside = (view)=> @addSubView view, 'aside'
+
+    JView::viewAppended.call this
+
+    @decorate()
+    @setLazyLoader 200
+
+    appendAside @referalBox       if KD.isLoggedIn() and not isPrivateGroup()
+    appendAside @groupDescription if isPrivateGroup()
+    appendAside @groupMembers     if isPrivateGroup() and hasListPermissions()
+    appendAside @groupListBox     if isKoding()
+    appendAside @topicsBox
+    appendAside @usersBox         if canListMembers()
+
+
+  pistachio:->
+    """
+    {{> @groupCoverView}}
+
+      {{> @inputWidget}}
+      {{> @feedWrapper}}
+      {{> @topWidgetWrapper}}
+
+
+      {{> @leftWidgetWrapper}}
+
+    """
+
+
+  activitySubmitted:->
+
+    appTop   = @getElement().offsetTop
+    listTop  = @feedWrapper.listWrapper.getElement().offsetTop
+    duration = @feedWrapper.pinnedListWrapper.getHeight() * .3
+    $('html, body').animate {scrollTop: appTop + listTop + 10}, {duration}
+
+
+  decorate:->
+
+    {entryPoint, roles} = KD.config
+
+    @unsetClass 'guest'
+    @setClass 'guest'     unless isMember()
+    @setClass 'loggedin'  if KD.isLoggedIn()
+
+    unless isMember()
+    then @inputWidget.hide()
+    else @inputWidget.show()
+
+    @_windowDidResize()
+
+
+  setTopicTag: (slug) ->
+
+    return  unless slug
+
+    KD.remote.api.JTag.one {slug}, null, (err, tag) =>
+      @inputWidget.input.setDefaultTokens tags: [tag]
+
+
+  unsetTopicTag: -> @inputWidget.input.setDefaultTokens tags: []
+
+
+  # ticker: if we ever need to put it back it's here. at least for a while.- SY
+    # @tickerBox        = new ActivityTicker

     # calculateTopOffset = =>
     #   KD.utils.wait 3000, =>
@@ -70,139 +150,5 @@

     # @groupListBox.on 'TopOffsetShouldBeFixed', calculateTopOffset

-    @decorate()

-    @setLazyLoader 200
-
-    @addSubView @groupCoverView
-    @addSubView @mainBlock
-    @addSubView @sideBlock
-
-    topWidgetPlaceholder  = new KDCustomHTMLView
-    leftWidgetPlaceholder = new KDCustomHTMLView
-
-    @mainBlock.addSubView topWidgetPlaceholder
-    @mainBlock.addSubView @inputWidget
-    @mainBlock.addSubView @feedWrapper
-
-    @sideBlock.addSubView @referalBox  if KD.isLoggedIn() and not @isPrivateGroup()
-    @sideBlock.addSubView leftWidgetPlaceholder
-    @sideBlock.addSubView @groupDescription if @isPrivateGroup()
-    @sideBlock.addSubView @groupMembers if @isPrivateGroup() and ("list members" in KD.config.permissions)
-    @sideBlock.addSubView @groupListBox  if KD.getGroup().slug is "koding"
-    @sideBlock.addSubView @topicsBox
-    @sideBlock.addSubView @usersBox if "list members" in KD.config.permissions
     # @sideBlock.addSubView @tickerBox
-
-    KD.getSingleton("widgetController").showWidgets [
-      { view: topWidgetPlaceholder,  key: "ActivityTop"  }
-      { view: leftWidgetPlaceholder, key: "ActivityLeft" }
-    ]
-
-  isPrivateGroup :->
-    {entryPoint} = KD.config
-    if entryPoint?.slug isnt "koding" and entryPoint?.type is "group" then yes else no
-
-  decorate:->
-    @unsetClass "guest"
-    {entryPoint, roles} = KD.config
-    @setClass "guest" unless "member" in roles
-    # if KD.isLoggedIn()
-    @setClass 'loggedin'
-    if entryPoint?.type is 'group' and 'member' not in roles
-    then @inputWidget.hide()
-    else @inputWidget.show()
-    # else
-    #   @unsetClass 'loggedin'
-    #   @inputWidget.hide()
-    @_windowDidResize()
-
-  setTopicTag: (slug) ->
-    return  if not slug or slug is ""
-    KD.remote.api.JTag.one {slug}, null, (err, tag) =>
-      @inputWidget.input.setDefaultTokens tags: [tag]
-
-  unsetTopicTag: ->
-    @inputWidget.input.setDefaultTokens tags: []
-
-
-class ActivityListContainer extends JView
-
-  constructor:(options = {}, data)->
-    options.cssClass = "activity-content feeder-tabs"
-
-    super options, data
-
-    @pinnedListController = new PinnedActivityListController
-      delegate    : this
-      itemClass   : ActivityListItemView
-      viewOptions :
-        cssClass  : "hidden"
-
-    @pinnedListWrapper = @pinnedListController.getView()
-
-    @pinnedListController.on "Loaded", =>
-      @togglePinnedList.show()
-      @pinnedListController.getListView().show()
-
-    @togglePinnedList = new KDCustomHTMLView
-      cssClass   : "toggle-pinned-list hidden"
-      # click      : KDView::toggleClass.bind @pinnedListWrapper, "hidden"
-
-    @togglePinnedList.addSubView new KDCustomHTMLView
-      tagName    : "span"
-      cssClass   : "title"
-      partial    : "Most Liked"
-
-    @controller = new ActivityListController
-      delegate          : @
-      itemClass         : ActivityListItemView
-      showHeader        : yes
-      # wrapper           : no
-      # scrollView        : no
-
-    @listWrapper = @controller.getView()
-    @filterWarning = new FilterWarning
-
-    @controller.ready => @emit "ready"
-
-  setSize:(newHeight)->
-    # @controller.scrollView.setHeight newHeight - 28 # HEIGHT OF THE LIST HEADER
-
-  viewAppended: ->
-    super
-    @togglePinnedList.show()  if @pinnedListController.getItemCount()
-
-  pistachio:->
-    """
-      {{> @filterWarning}}
-      {{> @togglePinnedList}}
-      {{> @pinnedListWrapper}}
-      {{> @listWrapper}}
-    """
-
-class FilterWarning extends JView
-
-  constructor:->
-    super cssClass : 'filter-warning hidden'
-
-    @warning   = new KDCustomHTMLView
-    @goBack    = new KDButtonView
-      cssClass : 'goback-button'
-      # todo - add group context here!
-      callback : => KD.singletons.router.handleRoute '/Activity'
-
-  pistachio:->
-    """
-    {{> @warning}}
-    {{> @goBack}}
-    """
-
-  showWarning:({text, type})->
-    partialText = switch type
-      when "search" then "Results for \"#{text}\""
-      else "You are now looking at activities tagged with ##{text}"
-
-    @warning.updatePartial "#{partialText}"
-
-    @show()


client: remove joinGroup_ util

client: remove joinGroup_ util

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/util/joinGroup_.coffee
+++ client/app/lib/util/joinGroup_.coffee
@@ -1,20 +0,0 @@
-remote = require('../remote')
-whoami = require './whoami'
-notify_ = require './notify_'
-
-module.exports = (groupName, callback) ->
-  return callback null unless groupName
-  user = whoami()
-
-  user.checkGroupMembership groupName, (err, isMember) ->
-    return callback err  if err
-    return callback null if isMember
-
-    #join to group
-    remote.api.JGroup.one { slug: groupName }, (err, currentGroup) ->
-      return callback err if err
-      return callback null unless currentGroup
-      currentGroup.join (err) ->
-        return callback err if err
-        notify_ "You have joined to #{groupName} group!", 'success'
-        return callback null


Fix jQuery promise failure handler API reference

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- client/app/lib/util/shortenUrl.coffee
+++ client/app/lib/util/shortenUrl.coffee
@@ -18,6 +18,6 @@
   request.done (data) ->
     callback data?.id or longUrl, data

-  request.error ({ status, statusText, responseText }) ->
+  request.fail ({ status, statusText, responseText }) ->
     console.error 'URL shortener error', status, statusText, responseText
     callback longUrl


fix previous commit

fix previous commit

--- client/app/views/calendar_view.coffee
+++ client/app/views/calendar_view.coffee
@@ -1,4 +1,5 @@
 app = require 'application'
+
 BaseView = require 'lib/base_view'
 EventSharingButtonView = require './pending_event_sharings_button'
 Header = require './calendar_header'
@@ -117,12 +118,14 @@
                 @cal.fullCalendar 'today'

         @calHeader.on 'month', =>
-            @clearViewComponents =>
-                @cal.fullCalendar 'changeView', 'month'
+            view = @cal.fullCalendar 'getView'
+            hash = view.intervalStart.format '[month]/YYYY/M'
+            app.router.navigate hash, true

         @calHeader.on 'week', =>
-            @clearViewComponents =>
-                @cal.fullCalendar 'changeView', 'agendaWeek'
+            view = @cal.fullCalendar 'getView'
+            hash = view.intervalStart.format '[week]/YYYY/M/DD'
+            app.router.navigate hash, true

         @calHeader.on 'list', =>
             @clearViewComponents ->


hide suggest incidents checkbox bc feature is broken

--- client/controllers/sourceModal.coffee
+++ client/controllers/sourceModal.coffee
@@ -162,8 +162,9 @@
     Template.instance().articleOrigin.get() is 'text'

   showEnhanceOption: ->
-    instance = Template.instance()
-    not instance.data.edit and instance.suggest
+    # instance = Template.instance()
+    # not instance.data.edit and instance.suggest
+    false

   showArticleInputs: ->
     not @edit and not @url


rtm: add lifecycle events for despose

rtm: add lifecycle events for despose

--- client/ide/lib/realtimemanager.coffee
+++ client/ide/lib/realtimemanager.coffee
@@ -290,8 +290,12 @@

   dispose: ->

+    @emit 'RealtimeManagerWillDespose'
+
     @realtimeDoc?.close()
     @isDisposed = yes
     @destroy()
     @readyState = 0
     @isReady = no
+
+    @emit 'RealtimeManagerDidDispose'


Fix mixin path resolution for string names

--- client/lanes/lib/MakeBaseClass.coffee
+++ client/lanes/lib/MakeBaseClass.coffee
@@ -13,7 +13,12 @@
 mixinModules = (klass) ->
     Lanes.lib.ModuleSupport.includeInto(klass)
     if klass::mixins
-        klass.include(Lanes.u.getPath(mixin, klass::FILE?.namespace)) for mixin in klass::mixins
+        for mixin in klass::mixins
+            if _.isString(mixin) and klass::FILE
+                part = _.classify(klass::FILE.path[1])
+                mixin = klass::FILE.namespace[part]?.Mixins[mixin] || Lanes[part]?.Mixins[mixin]
+            klass.include(mixin) if mixin
+

 extendProperties = (parent, child) ->
     child::extendedProperties = _.uniq(


fix indent

fix indent

--- src/adapter.coffee
+++ src/adapter.coffee
@@ -14,7 +14,7 @@
 #    limitations under the License.
 #

-  {Adapter} = require 'hubot'
+{Adapter} = require 'hubot'

 Symphony = require './symphony'
 {V2Message} = require './message'


Fix ship count when powerup. Fix https://github.com/yudachi/poi/issues/58

--- views/components/main/parts/teitoku-panel.cjsx
+++ views/components/main/parts/teitoku-panel.cjsx
@@ -87,6 +87,9 @@
       when '/kcsapi/api_req_kousyou/remodel_slot'
         @setState
           slotitemCount: Object.keys(window._slotitems).length
+      when '/kcsapi/api_req_kaisou/powerup'
+        @setState
+          shipCount: Object.keys(window._ships).length
   componentDidMount: ->
     window.addEventListener 'game.response', @handleResponse
   componentWillUnmount: ->


Update teitoku-panel.cjsx

Update teitoku-panel.cjsx

using error to notify alert

--- views/components/main/parts/teitoku-panel.cjsx
+++ views/components/main/parts/teitoku-panel.cjsx
@@ -155,10 +155,16 @@
         if config.get 'poi.mapstartcheck.ship'
           freeShipSlot = config.get 'poi.mapstartcheck.freeShipSlot'
           if @state.maxChara - @state.shipCount < freeShipSlot
-            toggleModal '船位检查', "船位剩余#{@state.maxChara - @state.shipCount}，出击注意！"
+            # toggleModal '船位检查', "船位剩余#{@state.maxChara - @state.shipCount}，出击注意！"
+            setTimeout =>
+              error "船位剩余#{@state.maxChara - @state.shipCount}，出击注意！"
+            , 700
         if config.get 'poi.mapstartcheck.item'
           if @state.maxSlotitem - @state.slotitemCount <= 0
-            toggleModal '装备检查', "装备已满，出击注意！"
+            # toggleModal '装备检查', "装备已满，出击注意！"
+            setTimeout =>
+              error "装备已满，出击注意！"
+            , 700
   componentDidMount: ->
     window.addEventListener 'game.response', @handleResponse
   componentWillUnmount: ->


Add pages to a sensible point at PageComponent


indentation fix

indentation fix

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -132,7 +132,7 @@
     @bugTagSwitch   = new KodingSwitch
       cssClass      : 'dark'
       defaultValue  : "bug" in postUserTags
-      callback      : (state)=>
+      callback      : (state) =>
         @tagStateChanged state, "bug", post

     modal.addSubView new KDLabelView
@@ -145,7 +145,7 @@

   tagStateChanged:(state, tagto, post)->
     {JTag} = KD.remote.api
-    JTag.one {"slug" : tagto}, (err, tag)=>
+    JTag.one "slug" : tagto, (err, tag)=>
       if state
         @addTagToPost post, tag
       else
@@ -168,7 +168,7 @@
     activity.modify options, (err)->
       KD.showError err if err

-  addTagToPost : (activity, tagToAdd, update = no) ->
+  addTagToPost : (activity, tagToAdd)->
     if not tagToAdd
       return new KDNotificationView title : "Tag not found!"

@@ -177,7 +177,7 @@
     body         += " |#:JTag:#{tagToAdd.getId()}|"

     if tags?.length > 0
-      newTags.push {id : tag.getId()} for tag in tags
+      newTags.push id : tag.getId() for tag in tags

     newTags.push id : tagToAdd.getId()


activity: refactor settings menu edit/delete post event listener binding

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -48,8 +48,8 @@

   getOwnerMenu: ->

-    @addMenuItem 'Edit Post', => @emit 'ActivityEditIsClicked'
-    @addMenuItem 'Delete Post', => @confirmDeletePost()
+    @addMenuItem 'Edit Post', @lazyBound 'emit', 'ActivityEditIsClicked'
+    @addMenuItem 'Delete Post', @bound 'confirmDeletePost'

     return @menu


StackSettings: InitiateNewStackWizard, creates a wizard based on the defined steps

--- client/admin/lib/views/groupstacksettings.coffee
+++ client/admin/lib/views/groupstacksettings.coffee
@@ -54,6 +54,27 @@
       else
         @replaceViews wizardView: data

+  initiateNewStackWizard: ->
+
+    NEW_STACK_STEPS = [
+      'stepSelectProvider'
+      'stepSetupCredentials'
+      'stepDefineStack'
+      'stepTestAndSave'
+    ]
+
+    steps = []
+
+    NEW_STACK_STEPS.forEach (step, index) =>
+      steps.push (data) =>
+        @replaceViewsWith "#{step}": {
+          callback: steps[index+1] or -> console.log 'LAST ONE'
+          cancelCallback: steps[index-1] or @bound 'initiateInitialView'
+          data
+        }
+
+    steps.first()
+

   createEditorPane: (content) ->


Added cleanup before and after tests

Added cleanup before and after tests

--- test/99_integration.coffee
+++ test/99_integration.coffee
@@ -84,7 +84,11 @@
 				host:      "localhost"
 				project:   "project"
 				env:       "development"
-			reporter.on "started", done
+			reporter.on "started", ->
+				reporter.del "services", (error) ->
+					throw error if error
+					done()
+
 			reporter.start()

 		after (done) ->
@@ -96,8 +100,10 @@
 			), ->
 				setTimeout ->
 					printServices ->
-						reporter.stop()
-						done()
+						reporter.del "services", (error) ->
+							throw error if error
+							reporter.stop()
+							done()
 				, 200

 		afterEach (done) ->


vertical align subtotal price with text.

--- public/views/cart_detail_view.coffee
+++ public/views/cart_detail_view.coffee
@@ -26,12 +26,14 @@

-
Subtotal

+
Subtotal

                   includes VAT, plus Shipping.

-              <%= subTotal %>
+
+                <%= subTotal %>
+

@@ -73,9 +75,12 @@
       .epages-cart-overlay table tfoot td {
         padding: 10px 10px 20px;
       }
+      .epages-cart-overlay-product-price-desc {
+        font-weight: bold;
+        font-size: 120%;
+      }
       .epages-cart-overlay-product-price {
-        font-weight: bold;
-        font-size: 135%;
+        vertical-align: top;
       }
       .epages-cart-overlay-product-shipping {
         font-size: 80%;


Use RSM in MAM

Use RSM in MAM

--- src/xmpp/pubsub_server.coffee
+++ src/xmpp/pubsub_server.coffee
@@ -747,6 +747,7 @@
         @queryId = @mamEl?.attrs?.queryid
         @start = @mamEl?.getChildText("start")
         @end = @mamEl?.getChildText("end")
+        @setRSM @mamEl

     matches: () ->
         @iq.attrs.type is 'get' &&


remove redundancy

remove redundancy

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -45,7 +45,6 @@
     t.ok row_dict = rows('rows', ['alpha', 'beta']), 'create rows'
     t.equal row_dict.layout, vector.axis.down, 'y axis'

-    t.ok row_tags = render_mock(row_dict).body.body, 'render rows'
     t.ok tags = render_mock(row_dict).body, 'render rows'
     t.equal get_label(tags), 'rows', 'row label'
     t.ok row_tags = tags.body, 'extract rows'
@@ -59,7 +58,6 @@
     t.ok col_dict = cols('cols', ['alpha', 'beta']), 'create cols'
     t.equal col_dict.layout, vector.axis.across, 'x axis'

-    t.ok col_tags = render_mock(col_dict).body.body, 'render cols'
     t.ok tags = render_mock(col_dict).body, 'render cols'
     t.equal get_label(tags), 'cols', 'col label'
     t.ok col_tags = tags.body, 'extract cols'
@@ -75,10 +73,14 @@
     t.equal get_label(body), header._LABEL, 'header label'
     t.end()

-  test "render buttons", (t) ->
+  test "render svg", (t) ->
+    t.ok row_dict = rows('rows', ['alpha', 'beta']), 'create rows'
+    t.equal row_dict.layout, vector.axis.down, 'y axis'
+
+    t.ok tags = render_mock(row_dict).body, 'render rows'
     t.end()

-  test "render svg", (t) ->
+  test "render buttons", (t) ->
     t.end()

   test "render name", (t) ->


Don't set a redirected location if we weren't redirected

--- src/turbolinks/visit.coffee
+++ src/turbolinks/visit.coffee
@@ -98,7 +98,7 @@
     @adapter.visitRequestProgressed?(this)

   requestCompletedWithResponse: (@response, redirectedToLocation) ->
-    @redirectedToLocation = Turbolinks.Location.box(redirectedToLocation)
+    @redirectedToLocation = Turbolinks.Location.box(redirectedToLocation) if redirectedToLocation?
     @adapter.visitRequestCompleted(this)

   requestFailedWithStatusCode: (statusCode, @response) ->


Fix LCM of negative arguments according to the ISO standard.

--- src/vocabulary/logic.coffee
+++ src/vocabulary/logic.coffee
@@ -82,8 +82,8 @@
     # 0∧123                          <=> 0
     # 123∧0                          <=> 0
     # ∧/⍬                            <=> 1
-    # ¯12∧18                         <=> 36
-    # 12∧¯18                         <=> 36
+    # ¯12∧18                         <=> ¯36
+    # 12∧¯18                         <=> ¯36
     # ¯12∧¯18                        <=> 36
     # 1.5∧2.5                        !!! DOMAIN ERROR
     dyad: real (y, x) ->
@@ -92,7 +92,7 @@
       p = x * y
       if p is 0 then return 0
       while y then [x, y] = [y, x % y] # Euclid's algorithm
-      Math.abs p / x # LCM(x, y) = x * y / GCD(x, y)
+      p / Math.abs x # LCM(x, y) = x * y / GCD(x, y)

   # Nor (`⍱`)


hide private functions

hide private functions

--- vendor/assets/javascripts/scribe/editor.coffee
+++ vendor/assets/javascripts/scribe/editor.coffee
@@ -1,3 +1,66 @@
+doSilently = (fn) ->
+  oldIgnoreDomChange = @ignoreDomChanges
+  @ignoreDomChanges = true
+  fn()
+  @ignoreDomChanges = oldIgnoreDomChange
+
+initListeners = ->
+  onEditOnce = =>
+  onEdit = =>
+    onEditOnce = _.once(onEdit)
+    return if @ignoreDomChanges
+    update.call(this)
+  onSubtreeModified = =>
+    return if @ignoreDomChanges
+    toCall = onEditOnce
+    _.defer( =>
+      toCall.call(null)
+    )
+  onEditOnce = _.once(onEdit)
+  @root.addEventListener('DOMSubtreeModified', onSubtreeModified)
+
+keepNormalized = (fn) ->
+  fn.call(this)
+  @doc.rebuildDirty()
+  @doc.forceTrailingNewline()
+
+trackDelta = (fn) ->
+  oldDelta = @doc.toDelta()
+  fn()
+  newDelta = @doc.toDelta()
+  decompose = newDelta.decompose(oldDelta)
+  compose = oldDelta.compose(decompose)
+  console.assert(compose.isEqual(newDelta), oldDelta, newDelta, decompose, compose)
+  @undoManager.record(decompose, oldDelta)
+  this.emit(ScribeEditor.events.TEXT_CHANGE, decompose) unless decompose.isIdentity()
+  return decompose
+
+update = ->
+  doSilently.call(this, =>
+    @selection.preserve( =>
+      return trackDelta.call(this, =>
+        Scribe.Document.normalizeHtml(@root)
+        lines = @doc.lines.toArray()
+        lineNode = @root.firstChild
+        _.each(lines, (line, index) =>
+          while line.node != lineNode
+            if line.node.parentNode == @root
+              newLine = @doc.insertLineBefore(lineNode, line)
+              lineNode = lineNode.nextSibling
+            else
+              @doc.removeLine(line)
+              return
+          @doc.updateLine(line)
+          lineNode = lineNode.nextSibling
+        )
+        while lineNode != null
+          newLine = @doc.appendLine(lineNode)
+          lineNode = lineNode.nextSibling
+      )
+    )
+  )
+
+
 class ScribeEditor extends EventEmitter2
   @editors: []

@@ -31,47 +94,27 @@
     @keyboard = new Scribe.Keyboard(this)
     @undoManager = new Scribe.UndoManager(this)
     @pasteManager = new Scribe.PasteManager(this)
-    this.initListeners()
+    initListeners.call(this)
     @ignoreDomChanges = false
     ScribeEditor.editors.push(this)

   disable: ->
-    this.doSilently( =>
+    doSilently.call(this, =>
       @root.setAttribute('contenteditable', false)
     )

   enable: ->
     if !@root.getAttribute('contenteditable')
-      this.doSilently( =>
+      doSilently.call(this, =>
         @root.setAttribute('contenteditable', true)
       )
-
-  initListeners: ->
-    onEditOnce = =>
-    onEdit = =>
-      onEditOnce = _.once(onEdit)
-      return if @ignoreDomChanges
-      this.update()
-    onSubtreeModified = =>
-      return if @ignoreDomChanges
-      toCall = onEditOnce
-      _.defer( =>
-        toCall.call(null)
-      )
-    onEditOnce = _.once(onEdit)
-    @root.addEventListener('DOMSubtreeModified', onSubtreeModified)
-
-  keepNormalized: (fn) ->
-    fn.call(this)
-    @doc.rebuildDirty()
-    @doc.forceTrailingNewline()

   applyDelta: (delta, external = true) ->
     # Make exception for systems that assume editors start with empty text
     if delta.startLength == 0 and @doc.length == 1 and @doc.trailingNewline
       return this.setDelta(delta)
     return if delta.isIdentity()
-    this.doSilently( =>
+    doSilently.call(this, =>
       @selection.preserve( =>
         console.assert(delta.startLength == @doc.length, "Trying to apply delta to incorrect doc length", delta, @doc, @root)
         index = 0       # Stores where the last retain end was, so if we see another one, we know to delete
@@ -120,28 +163,22 @@
     )

   deleteAt: (index, length) ->
-    this.doSilently( =>
-      @selection.preserve( =>
-        return this.trackDelta( =>
-          this.keepNormalized( =>
+    doSilently.call(this, =>
+      @selection.preserve( =>
+        return trackDelta.call(this, =>
+          keepNormalized.call(this, =>
             @doc.deleteText(index, length)
           )
         )
       )
     )
-
-  doSilently: (fn) ->
-    oldIgnoreDomChange = @ignoreDomChanges
-    @ignoreDomChanges = true
-    fn()
-    @ignoreDomChanges = oldIgnoreDomChange

   # formatAt: (Number index, Number length, String name, Mixed value) ->
   formatAt: (index, length, name, value) ->
-    this.doSilently( =>
-      @selection.preserve( =>
-        return this.trackDelta( =>
-          this.keepNormalized( =>
+    doSilently.call(this, =>
+      @selection.preserve( =>
+        return trackDelta.call(this, =>
+          keepNormalized.call(this, =>
             @doc.formatText(index, length, name, value)
           )
         )
@@ -158,10 +195,10 @@
     return @selection.getRange()

   insertAt: (index, text) ->
-    this.doSilently( =>
-      @selection.preserve( =>
-        return this.trackDelta( =>
-          this.keepNormalized( =>
+    doSilently.call(this, =>
+      @selection.preserve( =>
+        return trackDelta.call(this, =>
+          keepNormalized.call(this, =>
             @doc.insertText(index, text)
           )
         )
@@ -177,42 +214,6 @@
   setSelection: (range) ->
     @selection.setRange(range)

-  trackDelta: (fn) ->
-    oldDelta = @doc.toDelta()
-    fn()
-    newDelta = @doc.toDelta()
-    decompose = newDelta.decompose(oldDelta)
-    compose = oldDelta.compose(decompose)
-    console.assert(compose.isEqual(newDelta), oldDelta, newDelta, decompose, compose)
-    @undoManager.record(decompose, oldDelta)
-    this.emit(ScribeEditor.events.TEXT_CHANGE, decompose) unless decompose.isIdentity()
-    return decompose
-
-  update: ->
-    this.doSilently( =>
-      @selection.preserve( =>
-        return this.trackDelta( =>
-          Scribe.Document.normalizeHtml(@root)
-          lines = @doc.lines.toArray()
-          lineNode = @root.firstChild
-          _.each(lines, (line, index) =>
-            while line.node != lineNode
-              if line.node.parentNode == @root
-                newLine = @doc.insertLineBefore(lineNode, line)
-                lineNode = lineNode.nextSibling
-              else
-                @doc.removeLine(line)
-                return
-            @doc.updateLine(line)
-            lineNode = lineNode.nextSibling
-          )
-          while lineNode != null
-            newLine = @doc.appendLine(lineNode)
-            lineNode = lineNode.nextSibling
-        )
-      )
-    )
-

 window.Scribe ||= {}


fix bg

fix bg

--- vue/src/shared/services/ability_service.coffee
+++ vue/src/shared/services/ability_service.coffee
@@ -122,7 +122,7 @@

   canAnnounceThread: (discussion) ->
     @canAdministerGroup(discussion.group()) or
-      (Session.user().isMemberOf(group) and discussion.group().membersCanAnnounce)
+      (Session.user().isMemberOf(discussion.group()) and discussion.group().membersCanAnnounce)

   canAddMembersToGroup: (group) ->
     @canAdministerGroup(group) or


Added Compiled JS Check

Added Compiled JS Check

The change to the affected regex will ensure that we are seeing compiled
js, and not coffee.

--- test/streams/coffee.coffee
+++ test/streams/coffee.coffee
@@ -20,7 +20,7 @@
     s = new CoffeeFile stub
     s.on 'data', (chunk) -> d += chunk
     s.on 'end', ->
-      d.should.match /required to pass/
+      d.should.match /\/\* required to pass/
       return done()


fix test

fix test

--- test/test-conv-init.coffee
+++ test/test-conv-init.coffee
@@ -20,7 +20,10 @@
         it 'populates entities with found entities', ->
             conv._initFromConvStates init.conv_states
             eql 326, entity.count()
-            eql entity['110994664963851875523'], {fallback_name:'Martin Algesten'}
+            eql entity['110994664963851875523'], {
+                fallback_name:'Martin Algesten'
+                id:'110994664963851875523'
+            }

     describe 'count', ->


Use async batch in async iterator test

Use async batch in async iterator test

--- tests/iterator-test.coffee
+++ tests/iterator-test.coffee
@@ -16,10 +16,10 @@
       db = handle
       done()

-  it 'should insert batch data', ->
+  it 'should insert batch data', (done) ->
     batch = new leveldb.Batch
     batch.put "#{i}", value for i in [100..200]
-    db.writeSync batch
+    db.write batch, done

   it 'should open database', (done) ->
     leveldb.open filename, (err, handle) ->


fix syp allowing title search

fix syp allowing title search

--- static/embed/oab_embed.coffee
+++ static/embed/oab_embed.coffee
@@ -247,7 +247,7 @@
     this.panel window.location.search.split('panel=')[1].split('&')[0].split('#')[0]
   if window.location.search.indexOf('section=') isnt -1
     this.section window.location.search.split('section=')[1].split('&')[0].split('#')[0]
-  this.find() if this.data.doi or this.data.title or this.data.url
+  this.find() if this.data.doi or (this.plugin is 'instantill' and (this.data.title or this.data.url))
   return this

@@ -758,8 +758,12 @@
       else
         this.data.id = val

-  if not this.data.doi and not this.data.url and not this.data.pmid and not this.data.pmcid and not this.data.title and not this.data.id
-    _L.show '#_oab_error', '
❌ Sorry please provide the full DOI' + if this.plugin is 'instantill' then ', title, citation, PMID or PMC ID.
' else '
'
+  if not this.data.doi and (this.plugin is 'shareyourpaper' or (not this.data.url and not this.data.pmid and not this.data.pmcid and not this.data.title and not this.data.id))
+    if this.plugin is 'shareyourpaper'
+      delete this.data.title
+      delete this.data.url
+      delete this.data.id
+    _L.show '#_oab_error', '
❌ Sorry please provide ' + if this.plugin is 'instantill' then 'the full DOI, title, citation, PMID or PMC ID.
' else 'a valid DOI.
'
   else
     this.state() # should this just be part of loading?
     this.loading()


about page typo

about page typo

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -192,7 +192,7 @@
     teamTitle: 'The Asteroid Zoo Team'
     team:
       lewicki: 'President and Chief Engineer
Planetary Resources, Inc.'
-      beasley: 'Astrophysicis,
 Planetary Resources, Inc.'
+      beasley: 'Astrophysicist,
 Planetary Resources, Inc.'
       whyte: 'Director of Citizen Science
Adler Planetarium, Zooniverse'
       feldt: 'Zooniverse Educator
Zooniverse'
       vanSingel: 'Designer
Zooniverse'


some typo corrections

some typo corrections

--- app/locale/cs.coffee
+++ app/locale/cs.coffee
@@ -258,17 +258,18 @@
   about:
     who_is_codecombat: "Kdo je CodeCombat?"
     why_codecombat: "Proč CodeCombat?"
-    who_description_prefix: "společně přišli s projektem CodeCombat v roce 2013. V roce 2008 jsme Vytvořili také "
-    who_description_suffix: ", velmi úspěšnou webovou a IOS aplikaci pro učení psaní Japonských a Čínských znaků."
-    who_description_ending: "Nyní nastal čas naučit další uživatele programování."
-    why_paragraph_1: "Při vytváření Skritteru neznal George základy programování a byl neustále frustrován svou neschopností implementovat vlastní nápady. Zkoušel se naučit programovat, ale lekce byly na něj příliš pomalé. Jeho spolubydlící zkoušel Codeacademy při pokusu o rekvalifikaci, ale to jej příliš brzy omrzelo. Týden co týden se někdo z Georgových přátel pokoušel Codeacademy použít k výuce, ale po chvíli odpadl. Uvědomili jsme si, že se jedná o stejný problém, který jsme již vyřešili při tvorbě Skitteru: lidé se pokouší učit na pomalých, intenzivních teoretických lekcích, ale místo toho potřebují rychlé, ale obsáhlé praktické cvičení. Na tento problém máme řešení."
-    why_paragraph_2: "Potřebujete se naučit programovat? Nepotřebujete lekce, potřebuje příležitost psát spoustu kódu a při tom se u toho dobře bavit."
-    why_paragraph_3_prefix: "To je to, o čem musí programování být. Ne rádoby zábava typu"
+    who_description_prefix: "společně přišli s projektem CodeCombat v roce 2013. V roce 2008 jsme vytvořili také "
+    who_description_suffix: ", jedničku mezi webovými a IOS aplikacemi pro učení psaní japonských a čínských znaků."
+    who_description_ending: "Nyní nastal čas pomoci lidem s programováním."
+    why_paragraph_1: "Při vytváření Skritteru neznal George základy programování a byl neustále frustrován svou neschopností implementovat vlastní nápady. Zkoušel se naučit programovat, ale lekce programování byly na něj příliš pomalé. Jeho spolubydlící se rozhodl o rekvalifikaci a tak zkusil Codeacademy, ale brzy toho nechal s tím, že je to příliš velká nuda. Týden co týden se někdo z Georgových přátel pokoušel využít Codeacademyk učení programování, ale po chvíli odpadl. Uvědomili jsme si, že se jedná o stejný problém, který jsme již vyřešili při tvorbě Skitteru: lidé se pokouší učit na pomalých, intenzivních teoretických lekcích, ale místo toho potřebují rychlé, ale obsáhlé praktické cvičení. A na tento problém známe řešení."
+    why_paragraph_2: "Potřebujete se naučit programovat? Pak nepotřebujete lekce, potřebuje příležitost psát spoustu kódu a při tom se u toho dobře bavit."
+    why_paragraph_3_prefix: "To je to o čem musí programování být. Ne rádoby zábava typu"
     why_paragraph_3_italic: "hmm, další odznáček"
     why_paragraph_3_center: "ale nadšení typu"
     why_paragraph_3_italic_caps: "POČKEJ MAMI, MUSÍM DOKONČIT ÚROVEŇ!"
     why_paragraph_3_suffix: "Proto je CodeCombat opravdová multiplayer hra, ne lekce kurzu s herními odznáčky. Neskončí, dokud sami nepřestanete, což je tentokrát dobrá věc."
-    why_paragraph_4: "A jestli se máte stát závislými na nějaké hře, pak ať je to hra tato, a staňte se díky tomu kouzelníky a odborníky v této technické době. A mimochodem - je to zdarma."
+    why_paragraph_4: "A jestli se máte stát závislými na nějaké hře, pak ať je to hra tato, a staňte se díky tomu kouzelníky a odborníky v této technické době."
+    why_ending: "A mimochodem - je to zdarma. "
     why_ending_url: "Začněte kouzlit!"
     george_description: "CEO, obchodník, návrhář webů i her a šampión všech začátečníků programování."
     scott_description: "Výtečný programátor, softwarový architekt, kouzelník v kuchyni i pán financí. Scott je v týmu pan rozumný."


fixed indentation de.coffee

fixed indentation de.coffee

--- app/locale/de.coffee
+++ app/locale/de.coffee
@@ -12,13 +12,13 @@
 #    fork: "Fork"
     play: "Abspielen"

- units:
-   second: "Sekunde"
-   seconds: "Sekunden"
-   minute: "Minute"
-   minutes: "Minuten"
-   hour: "Stunde"
-   hours: "Stunden"
+  units:
+    second: "Sekunde"
+    seconds: "Sekunden"
+    minute: "Minute"
+    minutes: "Minuten"
+    hour: "Stunde"
+    hours: "Stunden"

   modal:
     close: "Schließen"


Update el.coffee

Update el.coffee

few additions and small correction in leaderboard section for better displaying

--- app/locale/el.coffee
+++ app/locale/el.coffee
@@ -12,14 +12,14 @@
     multiplayer: "Πολλαπλοί Παίκτες" # Not currently shown on home page
     for_developers: "Για προγραμματιστές" # Not currently shown on home page.
     or_ipad: "Ή κατεβάστε για iPad"
-#    hoc_class_code: "I Have a Class Code"
-#    hoc_enter: "Enter"
-#    hoc_title: "Hour of Code?"
+    hoc_class_code: "Έχω Κωδικό Τάξης "
+    hoc_enter: "Είσοδος"
+    hoc_title: "Ώρα του Κώδικα?"

   nav:
     play: "Επίπεδα" # The top nav bar entry where players choose which levels to play
     community: "Κοινότητα"
-#    courses: "Courses"
+    courses: "Μαθήματα"
     editor: "Συγγραφέας"
     blog: "Ιστολόγιο"
     forum: "Φόρουμ"
@@ -54,7 +54,7 @@

   play:
     play_as: "Παίξτε ως" # Ladder page
-#    compete: "Compete!" # Course details page
+    compete: "Αγωνίσου!" # Course details page
     spectate: "Θεατής" # Ladder page
     players: "παίκτες" # Hover over a level on /play
     hours_played: "ώρες παιχνιδιού" # Hover over a level on /play
@@ -65,7 +65,7 @@
     locked: "Κλειδωμένο"
     purchasable: "Διαθέσιμο για αγορά" # For a hero you unlocked but haven't purchased
     available: "Διαθέσιμο"
-#    skills_granted: "Skills Granted" # Property documentation details
+    skills_granted: "Παρεχόμενες Ικανότητες" # Property documentation details
     heroes: "Ήρωες" # Tooltip on hero shop button from /play
     achievements: "Επιτεύγματα" # Tooltip on achievement list button from /play
     account: "Λογαριασμός" # Tooltip on account button from /play
@@ -187,7 +187,7 @@
     password: "Κωδικός"
     message: "Μήνυμα"
     code: "Κώδικας"
-#    ladder: "Ladder"
+    ladder: "Κατατάξη"
     when: "Όταν"
     opponent: "Αντίπαλος"
     rank: "Κατηγορία"
@@ -221,10 +221,10 @@
     years: "χρόνια"

   play_level:
-#    completed_level: "Completed Level:"
-#    course: "Course:"
+    completed_level: "Ολοκληρωμένο Επίπεδο:"
+    course: "Μάθημα:"
     done: "Έτοιμο"
-#    next_level: "Next Level:"
+    next_level: "Επομένο Επίπεδο:"
     next_game: "Επόμενο παιχνίδι"
     show_menu: "Εμφάνιση μενού παιχνιδιού"
     home: "Αρχική" # Not used any more, will be removed soon.
@@ -254,7 +254,7 @@
     victory_sign_up: "Εγγράψου για ενημερώσεις"
     victory_sign_up_poke: "Θέλεις να λαμβάνεις τα τελευταία νέα μέσω e-mail; Δημιούργησε έναν δωρεάν λογαριασμό και θα σε κρατάμε ενήμερο!"
     victory_rate_the_level: "Βαθμολογήστε το επίπεδο: " # {change}
-    victory_return_to_ladder: "Επιστροφή στη Σκάλα"
+    victory_return_to_ladder: "Επιστροφή στη Κατατάξη"
     victory_play_continue: "Συνέχισε"
     victory_saving_progress: "Αποθήκευση προόδου"
     victory_go_home: "Πήγαινε στην Αρχική"
@@ -262,8 +262,8 @@
     victory_review_placeholder: "Πως σου φάνηκε το επίπεδο;"
     victory_hour_of_code_done: "Τελείωσες;"
     victory_hour_of_code_done_yes: "Ναι, έχω τελειώσει με την Hour of Code!"
-    victory_experience_gained: "Κερδισμενη Εμπειρία"
-    victory_gems_gained: "Κερδισμενα Πετραδια"
+    victory_experience_gained: "Εμπειρία"
+    victory_gems_gained: "Πετραδια"
     victory_new_item: "Νέο αντικείμενο"
     victory_viking_code_school: "Μπράβο σου, μόλις πέρασες ένα δύσκολο επίπεδο! Αν δεν είσαι ήδη προγραμματιστής, πρέπει να γίνεις. Είσαι σε καλό δρόμο για να γίνεις δεκτός στο Σχολείο Προγρααματισμού των Βίκινγκ, όπου μπορείς να πας τις ικανότητες στο επόμενο επίπεδο και να γίνεις ένας επαγγελματίας προγραμματιστής ιστού σε 14 weeks."
     victory_become_a_viking: "Γίνε Βίκιγκ"
@@ -371,11 +371,11 @@
   leaderboard:
     view_other_solutions: "Προβολή Πίνακα Πρωτοπόρων"
     scores: "Σκορ"
-    top_players: "Κορυφαίοι Παίκτες Κατά"
-    day: "Ημέρα"
-    week: "Αυτήν την εβδομάδα"
-    all: "Παντοτινά"
-    time: "Χρόνος"
+    top_players: "Κορυφαιοι Παικτες "
+    day: "Ημερας"
+    week: "Εβδομαδας"
+    all: "Ολων των Εποχων"
+    time: "τη διαρκεια της"
     damage_taken: "Ζημιά που δέχθηκες"
     damage_dealt: "Ζημιά που αντιμετώπησες"
     difficulty: "δυσκολία"
@@ -621,10 +621,10 @@
     cost_premium_server: "Το CodeCombat είναι δωρεάν για τα πέντε πρώτα επίπεδα, μετά τα οποία κοστίζει $9.99 Δολλάρια ΗΠΑ το μήνα για να έχετε πρόσβαση στα πάνω από 190 επίπεδα στους αποκλειστικούς για τη χώρα εξυπηρετητές."
     free_1: "Υπάρχουν πάνω από 100 ΔΩΡΕΑΝ επίπεδα που καλύπτουν όλα τα αντικείμενα."
     free_2: "Η μηνιαία συνδρομή παρέχει πρόσβαση σε βίντεο βοηθήματα και παραπάνω επίπεδα για εξάσκηση."
-#    free_3: "The CodeCombat content is divided into"
-#    free_4: "courses"
-#    free_5: ".  The first course is free, and about an hour of material."
-#    free_6: "Access to the additional courses can be unlocked with a one-time purchase."
+    free_3: "Το περιεχομένο του CodeCombat χωρίζεται σε"
+    free_4: "μαθήματα"
+    free_5: ".   Η πρώτη σειρά μαθημάτων είναι δωρεάν και περιέχει περίπου μια ώρα υλικού."
+    free_6: "Πρόσβαση σε πρόσθετα μαθημάτα μπορεί να αποκτηθεί με αγορά συνδρομής."
     teacher_subs_title: "Οι εκπαιδευτικοί παίρνουν δωρεάν συνδρομές!" # {change}
     teacher_subs_0: "Προσφέρουμε δωρεάν συνδρομές σε εκπαιδευτικούς για να μπορέσουν να αξιολογήσουν το παιχνίδι." # {change}
     teacher_subs_1: "παρακαλούμε συμπληρώστε το"
@@ -678,15 +678,15 @@
     approved_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή" # {change}
     approved_2: "εγκρίθηκε."
     approved_3: "Σας στείλαμε περισσότερες οδηγίες στο"
-#    approved_4: "Enroll your students on the"
-#    approved_5: "courses"
-#    approved_6: "page."
+    approved_4: "Γράψτε τους μάθητες σας στη"
+    approved_5: "μαθήματα"
+    approved_6: "σελίδα."
     denied_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή" # {change}
     denied_2: "απορίφθηκε."
     contact_1: "Παρακαλούμε επικοινωνείστε"
     contact_2: "αν έχετε περαιτέρω ερωτήσεις."
     description_1: "Δίνουμε δωρεάν συνδρομές σε εκπαιδευτικούς για να αξιολογήσουν το παιχνίδι. Μπορείτε να βρείτε περισσότερες πληροφορίες στις"
-#    description_1b: "You can find more information on our"
+    description_1b: "Μπορείτε να βρείτε περισσότερες πληροφορίες στη"
     description_2: "εκπαιδευτική"
     description_3: "σελίδα."
     description_4: "Παρακαλούμε, συμπληρώστε αυτή τη φόρμα και θα σας στείλουμε οδηγίες."
@@ -779,7 +779,7 @@
     view_profile: "Προβολή του προφίλ σου"

   keyboard_shortcuts:
-    keyboard_shortcuts: "Συντομεύσει πληκτρολογίοθ"
+    keyboard_shortcuts: "Συντομεύσεις πληκτρολογίου"
     space: "Κενό"
     enter: "Enter"
     press_enter: "πατείστε enter"
@@ -1289,9 +1289,9 @@
     quantity: "Ποσότητα"
     redeemed: "Εξαγοράστηκε"
     no_codes: "No codes yet!"
-#    you_can1: "You can"
-#    you_can2: "purchase a prepaid code"
-#    you_can3: "that can be applied to your own account or given to others."
+    you_can1: "Μπορείτε"
+    you_can2: "να αγοράσετε ένα προπληρωμένο κωδικό"
+    you_can3: "που θα χρησιμοποιήσετε στο δικό σας λογαριασμό ή θα το δώσετε σε άλλους."

   loading_error:
     could_not_load: "Σφάλμα φόρτωσης από τον εξυπηρετητή"


Update en.coffee

Update en.coffee

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -335,6 +335,8 @@
     tip_source_code: "I want to change the world but they would not give me the source code."
     tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
     tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
+    tip_miss_lunch: "Anyone who has lost track of time when using a computer knows the propensity to dream, the urge to make dreams come true and the tendency to miss lunch. - Tim Berners-Lee"
+

   game_menu:
     inventory_tab: "Inventory"


Trial requests localization typo

Trial requests localization typo

Adding missing period at end of approved sentence.

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -649,7 +649,7 @@
     being_reviewed_1: "Your application for a free trial subscription is being"
     being_reviewed_2: "reviewed"
     approved_1: "Your application for a free trial subscription was"
-    approved_2: "approved"
+    approved_2: "approved." #{change}
     approved_3: "Further instructions have been sent to"
     denied_1: "Your application for a free trial subscription has been"
     denied_2: "denied"


Update en.coffee

Update en.coffee

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -634,8 +634,10 @@
     level_tab_thangs_all: "All"
     level_tab_thangs_conditions: "Starting Conditions"
     level_tab_thangs_add: "Add Thangs"
+    config_thang: "Double click to configure a thang"
     delete: "Delete"
     duplicate: "Duplicate"
+    stop_duplicate: "Stop Duplicate"
     rotate: "Rotate"
     level_settings_title: "Settings"
     level_component_tab_title: "Current Components"


fi.coffee translated: achievements

fi.coffee translated: achievements

--- app/locale/fi.coffee
+++ app/locale/fi.coffee
@@ -1097,23 +1097,23 @@
     favorite_postfix: "."
     not_member_of_clans: "Ei klaanien jäsenyyksiä."

-#  achievements:
-#    last_earned: "Last Earned"
-#    amount_achieved: "Amount"
-#    achievement: "Achievement"
-#    category_contributor: "Contributor"
-#    category_ladder: "Ladder"
-#    category_level: "Level"
-#    category_miscellaneous: "Miscellaneous"
-#    category_levels: "Levels"
-#    category_undefined: "Uncategorized"
-#    current_xp_prefix: ""
-#    current_xp_postfix: " in total"
-#    new_xp_prefix: ""
-#    new_xp_postfix: " earned"
-#    left_xp_prefix: ""
-#    left_xp_infix: " until level "
-#    left_xp_postfix: ""
+  achievements:
+    last_earned: "Viimeisin saavutus"
+    amount_achieved: "Yhteensä"
+    achievement: "Saavutus"
+    category_contributor: "Avustamiset"
+    category_ladder: "Liiga"
+    category_level: "Taso"
+    category_miscellaneous: "Muut"
+    category_levels: "Tasot"
+    category_undefined: "Kategorisoimattomat"
+    current_xp_prefix: ""
+    current_xp_postfix: " yhteensä"
+    new_xp_prefix: ""
+    new_xp_postfix: " ansaittu"
+    left_xp_prefix: ""
+    left_xp_infix: " jäljellä tasoon "
+    left_xp_postfix: ""

 #  account:
 #    recently_played: "Recently Played"


add more translations #3

add more translations #3

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -621,14 +621,14 @@

   amazon_hoc:
     title: "Continuer à apprendre avec Amazon!"
-#    congrats: "Congratulations on conquering that challenging Hour of Code!"
-#    educate_1: "Now, keep learning about coding and cloud computing with AWS Educate, an exciting, free program from Amazon for both students and teachers. With AWS Educate, you can earn cool badges as you learn about the basics of the cloud and cutting-edge technologies such as gaming, virtual reality, and Alexa."
-#    educate_2: "Learn more and sign up here"
-#    future_eng_1: "You can also try to build your own school facts skill for Alexa"
-#    future_eng_2: "here"
-#    future_eng_3: "(device is not required). This Alexa activity is brought to you by the"
-#    future_eng_4: "Amazon Future Engineer"
-#    future_eng_5: "program which creates learning and work opportunities for all K-12 students in the United States who wish to pursue computer science."
+    congrats: "Félicitations pour la réussite de cette stimulante Heure de Code!"
+    educate_1: "Maintenant, continue à apprendre à programmer et utiliser l'informatique dans les nuages avec AWS Educate, un passionant programme d'éducation gratuit fourni par Amazon pour les étudiants et les professeurs. Avec AWS Educate, vous pouvez gagner des badges cool pendant que tu apprends les bases de l'informatique dans les nuages et des technologies de pointe comme le jeu vidéo, la réalité virtuelle, et Alexa."
+    educate_2: "Apprenez plus en vous inscrivant ici"
+    future_eng_1: "Vou pouvez aussi essayer de construire votre propre école concernant Alexa"
+    future_eng_2: "ici"
+    future_eng_3: "(un appareil n'est pas requis). Cette activité Alexa a été élaborée pour vous par"
+    future_eng_4: "Future Ingénieur Amazon"
+    future_eng_5: "programme qui crée des opportunités d'apprentissage et de travail pour tous les étudiants jusqu'au baccalauréat au États-Unis qui veulent poursuivre des études d'informatique."

   play_game_dev_level:
     created_by: "Crée par {{name}}"
@@ -802,28 +802,28 @@
     wallOfDarkness_name: "WLe mur de la Pénombre"
     wallOfDarkness_description: "Se cacher derrière un mur d'ombres pour prévenir le regard d'yeux indiscrets."

-#  premium_features:
-#    get_premium: "Get
CodeCombat
Premium" # Fit into the banner on the /features page
-#    master_coder: "Become a Master Coder by subscribing today!"
-#    paypal_redirect: "You will be redirected to PayPal to complete the subscription process."
-#    subscribe_now: "Subscribe Now"
-#    hero_blurb_1: "Get access to __premiumHeroesCount__ super-charged subscriber-only heroes! Harness the unstoppable power of Okar Stompfoot, the deadly precision of Naria of the Leaf, or summon \"adorable\" skeletons with Nalfar Cryptor."
-#    hero_blurb_2: "Premium Warriors unlock stunning martial skills like Warcry, Stomp, and Hurl Enemy. Or, play as a Ranger, using stealth and bows, throwing knives, traps! Try your skill as a true coding Wizard, and unleash a powerful array of Primordial, Necromantic or Elemental magic!"
-#    hero_caption: "Exciting new heroes!"
-#    pet_blurb_1: "Pets aren't just adorable companions, they also provide unique functionality and methods. The Baby Griffon can carry units through the air, the Wolf Pup plays catch with enemy arrows, the Cougar is fond of chasing ogres around, and the Mimic attracts coins like a magnet!"
-#    pet_blurb_2: "Collect all the pets to discover their unique abilities!"
-#    pet_caption: "Adopt pets to accompany your hero!"
-#    game_dev_blurb: "Learn game scripting and build new levels to share with your friends! Place the items you want, write code for unit logic and behavior, and see if your friends can beat the level!"
-#    game_dev_caption: "Design your own games to challenge your friends!"
-#    everything_in_premium: "Everything you get in CodeCombat Premium:"
-#    list_gems: "Receive bonus gems to buy gear, pets, and heroes"
-#    list_levels: "Gain access to __premiumLevelsCount__ more levels"
-#    list_heroes: "Unlock exclusive heroes, include Ranger and Wizard classes"
-#    list_game_dev: "Make and share games with friends"
-#    list_web_dev: "Build websites and interactive apps"
-#    list_items: "Equip Premium-only items like pets"
-#    list_support: "Get Premium support to help you debug tricky code"
-#    list_clans: "Create private clans to invite your friends and compete on a group leaderboard"
+  premium_features:
+    get_premium: "Abonnes-toi à
CodeCombat
Premium" # Fit into the banner on the /features page
+    master_coder: "Devient un Maître du code en t'abonnant aujourd'hui!"
+    paypal_redirect: "TU seras redirigé vers PayPal pour compléter le processus d'abonnement."
+    subscribe_now: "Abonnes-toi Maintenant"
+    hero_blurb_1: "Aie accès aux __premiumHeroesCount__ super-héros accessibles uniquement aux abonnés! Libère l'imparable pouvoir de Okar le pied frappeur, la précision mortelle de naria de la Feuille, ou convoque les \"adorables\" squelettes avec Nalfar l'invisible."
+    hero_blurb_2: "Les Guerriers Premium  débloquent d'impressionantes capacités partiales comme Le cri de Guerre, Le piètinement, et le Lancer d'ennemi. Ou, joue un Ranger, en utilisant la discrétion et des arcs, le lancer de couteaux, et les pièges! Essaie tes pouvoirs en tant que vrai Magicien du code, et libère une puissante magie primordiale, nécromantienne, ou élémentaire!"
+    hero_caption: "D'excitant nouveaux héros!"
+    pet_blurb_1: "Les animaux de compagnie ne sont pas juste d'adorables compagnions, ils fournissent aussi d'uniques fonctionnalités et méthodes. Le Bébé Griffon peut transporter des unités dans les airs, le louveteau joue en attrapant les flèches des ennemis, le Cougar adore chasser les ogres aux alentours, et le singe attire les pièces comme un aimant!"
+    pet_blurb_2: "Collectionne tous les animaux de compagnie pour découvrir leurs capacités uniques!"
+    pet_caption: "Adopte des animaux de compagnie pour accompagner votre héro!"
+    game_dev_blurb: "Apprend le scripting de jeu et construis de nouveaux niveaux à partager avec tes amis! Place les eléments que tu veux,écris du code pour ajouter une logique et un comportement aux unités créées, et vois si tes amis peuvent battre ce niveau!"
+    game_dev_caption: "Crée tes propres jeux pour défier tes amis!"
+    everything_in_premium: "Tout ce que tu auras avec CodeCombat Premium:"
+    list_gems: "Reçois des gemmes bonus pour acheter de l'équipement, des animaux de compagnie, et des héros"
+    list_levels: "Aie accès à __premiumLevelsCount__  niveaux supplémentaires"
+    list_heroes: "Débloque des héros exclusifs, incluant les classes de Ranger et Magicien"
+    list_game_dev: "Crée et partage des jeux avec des amis"
+    list_web_dev: "Construit des sites web et des applications interactives"
+    list_items: "Equipe-toi d'eléments exclusifs à l'abonnement Premium comme des animaux de compagnie"
+    list_support: "Aie accès au support Premium pour t'aider à décotiquer du code difficile"
+    list_clans: "Crée des clans privés pour inviter tes amis et effectue des compétitions de groupes via un tableau de bord"

   choose_hero:
     choose_hero: "Choisissez votre héros"
@@ -864,7 +864,7 @@
   skill_docs:
     function: "fonction" # skill types
     method: "méthode"
-#    snippet: "snippet"
+    snippet: "fragment"
     number: "nombre"
     array: "tableau"
     object: "objet"
@@ -910,7 +910,7 @@
     editor_config_behaviors_description: "Ferme automatiquement les accolades, parenthèses, et chaînes de caractères."

   about:
-#    learn_more: "Learn More"
+    learn_more: "En apprendre Plus"
     main_title: "Si tu veux apprendre la programmation, tu dois écrire beaucoup de code."
     main_description: "Chez CodeCombat, notre but est d'assurer que tu le fasses avec un sourire."
     mission_link: "Mission"


Update he.coffee

Update he.coffee

finished the essentials and I took my time to check the translation, I wasn't active since I worked onmy projects now the steps remaining are
1) finishing the miscellenious translations (teachers, community, editors etc...)
2) fixing the right to left problem (probably by just using the mechanics of the text editor differently[reorganizing brackets, dots, commas, language mixed sentences and so on...])
3) altering other files to support sending the subscription message according to user's gender since there is no way to representboth gender in the same form (Could you please direct me to the files that I need to edit to send the message?)

--- app/locale/he.coffee
+++ app/locale/he.coffee
@@ -64,7 +64,7 @@
     achievements: "הישגים" # Tooltip on achievement list button from /play
     account: "חשבון" # Tooltip on account button from /play
     settings: "הגדרות" # Tooltip on settings button from /play
-#    poll: "Poll" # Tooltip on poll button from /play
+    poll: "סקר" # Tooltip on poll button from /play
     next: "הבא" # Go from choose hero to choose inventory before playing a level
     change_hero: "שנה גיבור" # Go back from choose inventory to choose hero
     choose_inventory: "לבש כלים"
@@ -97,7 +97,7 @@
     blurb: ".אתה מתקדם מצויין! ספר למישהו כמה למדת"
     email_invalid: ".כתובת המייל שהוזנה שגויה"
     form_blurb: "!הזן כתובת מייל ואנחנו נראה להם"
-    form_label: "כתובת מייל"
+    form_label: "כתובת דואר אלקטרוני"
     placeholder: "כתובת המייל"
     title: "עבודה מצויינת, מתלמד"
     tell_friend: "ספר לחברים"
@@ -325,7 +325,7 @@
 #    tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
     tip_superpower: ".תכנות זה הדבר הקרוב ביותר שיש לנו לכוחות על"
 #    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
-#    tip_no_code: "No code is faster than no code."
+    tip_no_code: "לא קוד מהיר יותר מקוד של לא."
     tip_code_never_lies: "קוד אף פעם לא משקר, הערות לפעמים כן. - רון ג'פריס"
 #    tip_reusable_software: "Before software can be reusable it first has to be usable."
 #    tip_optimization_operator: "Every language has an optimization operator. In most languages that operator is ‘//’"
@@ -394,7 +394,7 @@
     prompt_body: "?רוצה לקבל עוד"
     prompt_button: "היכנס לחנות"
     recovered: ".רכישה אחרונה שוחזרה. טען את הדף מחדש בבקשה"
-#    price: "x3500 / mo"
+    price: "x3500 \ לחודש"

   subscribe:
     comparison_blurb: ".CodeCombatחדד את כישוריך עם מנוי ל"
@@ -468,7 +468,7 @@
     lua_blurb: ".שפה לכתיבת תסריטי משחק"
     io_blurb: ".פשוטה אך מעורפלת"
     status: "סטטוס"
-#    hero_type: "Type"
+    hero_type: "סוג"
     weapons: "נשקים"
     weapons_warrior: "חרבות, טווח קצר - ללא קסם"
     weapons_ranger: "קשתות, אקדחים - טווח ארוך, ללא קסם"
@@ -564,11 +564,11 @@
     michael_blurb: "מנהל מערכת"
     matt_title: "מתכנת"
     matt_blurb: "רוכב אופניים"
-#    cat_title: "Chief Artisan"
-#    cat_blurb: "Airbender"
-#    josh_title: "Game Designer"
-#    josh_blurb: "Floor Is Lava"
-#    jose_title: "Music"
+    cat_title: "צ'יף אמן"
+    cat_blurb: "כשף אוויר"
+    josh_title: "מעצב משחק"
+    josh_blurb: "ארצפה היא לבה"
+    jose_title: "מוזיקה"
 #    jose_blurb: "Taking Off"
 #    retrostyle_title: "Illustration"
 #    retrostyle_blurb: "RetroStyle Games"
@@ -650,17 +650,17 @@
     autosave: "שינויים נשמרו אוטומטית"
     me_tab: "אני"
     picture_tab: "תמונה"
-#    delete_account_tab: "Delete Your Account"
-#    wrong_email: "Wrong Email"
+    delete_account_tab: "מחק את חשבונך"
+    wrong_email: "כתובת מייל שגויה."
     upload_picture: "העלה תמונה"
-#    delete_this_account: "Delete this account permanently"
+    delete_this_account: "מחק את חשבון זה לתמיד"
     god_mode: "מצב אל"
     password_tab: "סיסמה"
     emails_tab: "אימיילים"
     admin: "אדמין"
     new_password: "סיסמה חדשה"
     new_password_verify: "חזור על הסיסמה שנית"
-#    type_in_email: "Type in your email to confirm the deletion"
+    type_in_email: "הזן את כתובת המייל שלך על מנת לבצע את המחיקה"
     email_subscriptions: "הרשמויות אימייל"
     email_subscriptions_none: ".אין מנויי מייל"
     email_announcements: "הודעות"
@@ -751,13 +751,13 @@
     thang_title: "עורך ת'אנג"
     level_title: "עורך שלבים"
     achievement_title: "עורך הישגים"
-#    poll_title: "Poll Editor"
+    poll_title: "עורך סקרים"
     back: "חזרה"
     revert: "שחזר"
     revert_models: "שחזר מודלים"
     pick_a_terrain: "בחר סוג שטח"
     dungeon: "צינוק"
-#    indoor: "Indoor"
+    indoor: "בפנים"
     desert: "מדבר"
     grassy: "עשבי"
     small: "קטן"
@@ -812,13 +812,13 @@
     new_level_title_login: "התחבר כדי ליצור שלב חדש"
     new_achievement_title: "צור הישג חדש"
     new_achievement_title_login: "התחבר כדי ליצור הישג חדש"
-#    new_poll_title: "Create a New Poll"
-#    new_poll_title_login: "Log In to Create a New Poll"
+    new_poll_title: "צור סקר חדש"
+    new_poll_title_login: "התחבר כדי ליצור סקר חדש"
     article_search_title: "חפש כותרות כאן"
     thang_search_title: "חפש סוגי ת'אנגס"
     level_search_title: "חפש שלבים כאן"
     achievement_search_title: "חפש הישגים"
-#    poll_search_title: "Search Polls"
+    poll_search_title: "חפש סקרים"
     read_only_warning2: ".הערה: אין באפשרותך לשמור את העריכות שבוצעו כאן, אתה לא מחובר למערכת"
     no_achievements: "אין הישגים שנוספו לשלב זה עד כה"
 #    achievement_query_misc: "Key achievement off of miscellanea"
@@ -826,14 +826,14 @@
     level_completion: "סיום שלב"
     pop_i18n: "I18N אכלס"
     tasks: "מטלות"
-#    clear_storage: "Clear your local changes"
+    clear_storage: "נקה את השינויים המקומיים"

   article:
     edit_btn_preview: "צפה בכותרת"
     edit_article_title: "ערוך כותרת"

-#  polls:
-#    priority: "Priority"
+  polls:
+    priority: "עדיפות"

   contribute:
     page_title: "תרימה"
@@ -937,8 +937,8 @@
     tutorial_play: "הדרכה"
     tutorial_recommended: "מומלץ אם לא שיחקת לפני"
     tutorial_skip: "דלג על ההדרכה"
-#    tutorial_not_sure: "Not sure what's going on?"
-#    tutorial_play_first: "Play the Tutorial first."
+    tutorial_not_sure: "לא בטוח מה הולך?"
+    tutorial_play_first: "הרץ את ההדרכה"
     simple_ai: "בינה מלאכותית פשוטה"
     warmup: "חימום"
     friends_playing: "חברים משחקים"
@@ -953,7 +953,7 @@
     tournament_rules: "חוקי תחרות"
 #    tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
 #    tournament_blurb_criss_cross: "Win bids, construct paths, outwit opponents, grab gems, and upgrade your career in our Criss-Cross tournament! Check out the details"
-#    tournament_blurb_blog: "on our blog"
+    tournament_blurb_blog: "בבלוג שלנו"
     rules: "חוקים"
     winners: "מנצחים"

@@ -1013,24 +1013,24 @@
 #    status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
 #    status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"

-#  account_invoices:
-#    amount: "Amount in US dollars"
-#    declined: "Your card was declined"
-#    invalid_amount: "Please enter a US dollar amount."
+  account_invoices:
+    amount: "כמות בדולרים (אמריקאיים)"
+    declined: "הכרטיסשלך בוטל"
+    invalid_amount: "אנא הזן כמות דולרים (אמריקאיים)"
 #    not_logged_in: "Log in or create an account to access invoices."
 #    pay: "Pay Invoice"
-#    purchasing: "Purchasing..."
-#    retrying: "Server error, retrying."
-#    success: "Successfully paid. Thanks!"
+    purchasing: "מבצע רכישה..."
+    retrying: "אירעה שגיאה בשרת, מנסה מחדש..."
+    success: "שולם בהצלחה, תודה!"

   loading_error:
     could_not_load: "אירעה שגיאה בזמן טעינה מהשרת"
     connection_failure: ".חיהור נכשל"
-#    unauthorized: "You need to be signed in. Do you have cookies disabled?"
-#    forbidden: "You do not have the permissions."
+    unauthorized: "אתה צריך להתחבר. האם חסמת קבצי cookies?"
+    forbidden: "אין לך את ההרשאה"
     not_found: ".לא נמצא"
     not_allowed: ".פונקציה לא מותרת"
-#    timeout: "Server timeout."
+    timeout: "Server timeout."
 #    conflict: "Resource conflict."
 #    bad_input: "Bad input."
     server_error: ".שגיאת שרת"
@@ -1057,8 +1057,8 @@
     systems: "מערכות"
     component: "מרכיב"
     components: "מרכיבים"
-#    thang: "Thang"
-#    thangs: "Thangs"
+    thang: "Thang"
+    thangs: "Thangים"
 #    level_session: "Your Session"
 #    opponent_session: "Opponent Session"
     article: "כותרות"
@@ -1083,8 +1083,8 @@
     feedback: "פידבק"
     payment_info: "מידע תשלום"
     campaigns: "מסעות"
-#    poll: "Poll"
-#    user_polls_record: "Poll Voting History"
+    poll: "סקר"
+    user_polls_record: "היסטוריית הצבעות בסקרים"

   delta:
     added: "נוסף"
@@ -1192,16 +1192,16 @@
 #    featured: "Featured"
 #    not_featured: "Not Featured"
     looking_for: ":מחפש"
-#    last_updated: "Last updated:"
+    last_updated: "עודכן לאחרונה ב:"
     contact: "צור קשר"
 #    active: "Looking for interview offers now"
 #    inactive: "Not looking for offers right now"
 #    complete: "complete"
-#    next: "Next"
-#    next_city: "city?"
-#    next_country: "pick your country."
-#    next_name: "name?"
-#    next_short_description: "write a short description."
+    next: "הבא"
+    next_city: "עיר?"
+    next_country: "בחר את המדינה שלך."
+    next_name: "שם?"
+    next_short_description: "כתוב תיאור קצר."
 #    next_long_description: "describe your desired position."
 #    next_skills: "list at least five skills."
 #    next_work: "chronicle your work history."
@@ -1342,14 +1342,14 @@
     av_usersearch_search: "חיפוש"
 #    av_title: "Admin Views"
 #    av_entities_sub_title: "Entities"
-#    av_entities_users_url: "Users"
+    av_entities_users_url: "משתמשים"
 #    av_entities_active_instances_url: "Active Instances"
-#    av_entities_employer_list_url: "Employer List"
+    av_entities_employer_list_url: "רשימת מעסיק"
 #    av_entities_candidates_list_url: "Candidate List"
 #    av_entities_user_code_problems_list_url: "User Code Problems List"
-#    av_other_sub_title: "Other"
+    av_other_sub_title: "אחר"
 #    av_other_debug_base_url: "Base (for debugging base.jade)"
-#    u_title: "User List"
-#    ucp_title: "User Code Problems"
-#    lg_title: "Latest Games"
-#    clas: "CLAs"
+    u_title: "רשימת משתמשים"
+    ucp_title: "בעיות משתמשים בקוד"
+    lg_title: "המשחקים הכי חדשים"
+    clas: "CLAs"


Update id.coffee (#5949)

Update id.coffee (#5949)

Added a new translation

Co-authored-by: Bachrul uluum <70f25fea9ad661844aa8e67f2d257e8329f3a792@users.noreply.github.com>

--- app/locale/id.coffee
+++ app/locale/id.coffee
@@ -168,8 +168,8 @@

   nav:
 #    educators: "Educators"
-#    follow_us: "Follow Us"
-#    general: "General"
+    follow_us: "Ikuti Kami"
+    general: "Utama" #General menu
     map: "Peta"
     play: "Tingkatan" # The top nav bar entry where players choose which levels to play
     community: "Komunitas"
@@ -189,7 +189,7 @@
     twitter_follow: "Ikuti"
     my_classrooms: "Kelasku"
     my_courses: "Kursusku"
-#    my_teachers: "My Teachers"
+    my_teachers: "Guruku"
     careers: "Karir"
     facebook: "Facebook"
     twitter: "Twitter"
@@ -209,12 +209,11 @@
     resource_hub: "Pusat Sumber Daya"
     apcsp: "Fundamental AP CS"
     parent: "Orang Tua"
-#    browser_recommendation: "For the best experience we recommend using the latest version of Chrome. Download the browser here!"
-
+    browser_recommendation: "Untuk pengalaman yang lebih baik, kami merekomendasikan menggunakan browser chrome terbaru. Download browser disini"
   modal:
     close: "Tutup"
     okay: "Baik"
-#    cancel: "Cancel"
+    cancel: "Batal"

   not_found:
     page_not_found: "Laman tidak ditemukan"
@@ -351,7 +350,7 @@
     sign_in_with_facebook: "Masuk dengan Facebook"
     sign_in_with_gplus: "Masuk dengan Google"
     signup_switch: "Ingin membuat akun?"
-#    accounts_merge_confirmation: "There is an account associated with the email from this Google account. Would you like to merge these accounts?"
+    accounts_merge_confirmation: "Akun tersebut telah digunakan oleh akun google yang lain. Apakah anda ingin menggabungkan kedua akun tersebut?"

   signup:
     complete_subscription: "Berlanggangan Penuh"
@@ -497,7 +496,7 @@
     rejected: "Ditolak"
     withdrawn: "Ditarik"
     accept: "Terima"
-#    accept_and_save: "Accept&Save"
+    accept_and_save: "Terima&Simpan"
     reject: "Tolak"
     withdraw: "Tarik"
     submitter: "Yang Mengajukan"
@@ -1009,8 +1008,8 @@
     javascript_blurb: "Bahasa untuk web. (Tidak sama dengan Java.)"
     coffeescript_blurb: "Sintaksis Javascript yang lebih bagus"
     lua_blurb: "Bahasa untuk Skrip Permainan"
-    java_blurb: "(Hanya Pelanggan) Android dan perusahaan."
-#    cpp_blurb: "(Subscriber Only) Game development and high performance computing."
+    java_blurb: "(Khusus Pelanggan) Android dan perusahaan."
+    cpp_blurb: "(Khusus Pelanggan) Pengembangan game dan komputasi kinerja tinggi."
     status: "Status"
     weapons: "Senjata"
     weapons_warrior: "Pedang - Jarak Dekat, Tanpa Sihir"
@@ -1625,10 +1624,10 @@
     update_old_classroom_detail_2: "dan berikan siswa-siswa Kelas Kode yang baru muncul"
     view_assessments: "Lihat Penilaian"
     view_challenges: "lihat level tantangan"
-#    view_ranking: "view ranking"
-#    ranking_position: "Position"
-#    ranking_players: "Players"
-#    ranking_completed_leves: "Completed levels"
+    view_ranking: "Lihat Peringkat"
+    ranking_position: "Posisi"
+    ranking_players: "Pemain"
+    ranking_completed_leves: "Peringkat Diselesaikan "
     challenge: "Tantangan:"
     challenge_level: "Level Tantangan:"
     status: "Status:"