Removed old advice.coffee.

Removed old advice.coffee.

--- lib/server/browser/dom/advice.coffee
+++ lib/server/browser/dom/advice.coffee
@@ -1,307 +0,0 @@
-# TODO: form handling
-# TODO: JSDOM is missing defaultValue and defaultChecked setters for HTMLInputElement
-# TODO: JSDOM is missing defaultValue for HTMLTextAreaElement
-# TODO: entity reference, notationnode?
-# TODO: How should we handle cookie?
-
-isDOMNode = (node) -> (node?.ELEMENT_NODE == 1)
-
-# dom - the jsdom dom implementation
-# wrapper - must have emit and nodes
-# TODO: rename params, cause dom is the jsdom dom, wrappers is DOM.  that is dumb.
-exports.addAdvice = (dom, wrapper) ->
-    # TODO: change event names to DOMMethod and DOMProperty
-
-    # func gets passed the original return value and the 'this' value.
-    # If func returns true, then we will echo this property update.
-    # Otherwise, we just return the rv.
-    wrapProperty = (obj, prop, func) ->
-        originalSetter = obj.__lookupSetter__(prop)
-        if !originalSetter?
-            throw new Error "Missing a setter for #{prop}"
-        obj.__defineSetter__(prop, (value) ->
-            #console.log("#{prop} called")
-            rv = originalSetter.call(this, value)
-            if !func? || func(this, arguments, rv)
-                if value?.__nodeID?
-                    value = wrapper.nodes.get(value.__nodeID)
-                params =
-                    targetID : this.__nodeID
-                    prop : prop
-                    value : value
-                wrapper.emit('DOMPropertyUpdate', params)
-            return rv
-        )
-
-    wrapMethod = (obj, name, func) ->
-        originalMethod = obj[name]
-        obj[name] = ->
-            #console.log("#{name} called #{arguments[0]}")
-            rv = originalMethod.apply(this, arguments)
-            # Don't allow any operations on scripts to get to client.
-            if (rv?.tagName?.toLowerCase() == 'script') ||
-               (this?.tagName?.toLowerCase() == 'script')
-                return rv
-            if isDOMNode(rv) && (rv.__nodeID == undefined)
-                wrapper.nodes.add(rv)
-            if !func? || func(this, arguments, rv)
-                rvID = if rv? then rv.__nodeID else null
-                params =
-                    targetID : this.__nodeID
-                    rvID : rvID
-                    method : name
-                    args : wrapper.nodes.scrub(arguments)
-                wrapper.emit('DOMUpdate', params)
-            return rv
-
-    # w3c Level 2 Core
-    # NOTE: we don't wrap appendChild because JSDOM implements it using
-    # insertBefore, so wrapping it would cause duplicate instructions.
-    # We don't wrap cloneNode because it calls already wrapped DOM
-    # methods behind the scenes
-    for method in ['insertBefore', 'replaceChild', 'removeChild']
-        wrapMethod(dom.Node.prototype, method)
-    wrapProperty(dom.Node.prototype, 'nodeValue')
-
-    # Note: have to use this methods variable for multi-line arrays in a for/in
-    # loop or else coffee-script complains.
-    methods = ['setAttribute', 'setAttributeNS',
-               'removeAttribute', 'removeAttributeNS']
-    for method in methods
-        do (method) ->
-            wrapMethod dom.Element.prototype, method, (elem, args, rv) ->
-                tagName = elem.tagName.toLowerCase()
-                attr = null
-                value = null
-                if /NS$/.test(method)
-                    attr = args[1].toLowerCase()
-                    if args.length > 2
-                        value = args[1]
-                else
-                    attr = args[0].toLowerCase()
-                    if args.length > 1
-                        value = args[1]
-                if attr == 'src'
-                    if tagName == 'iframe' || tagName == 'frame'
-                        # There is still special handling to be done in this
-                        # case, but we need to do it after the
-                        # HTMLFrameElement's function has been called.
-                        return false
-                    console.log("FOUND A URL IN ADVICE TODO: RESOURCEPROXY")
-                if /data-component/.test(attr)
-                    console.log("Found a component")
-                    params =
-                        nodeID: elem.__nodeID
-                        componentName : null
-                        opts : {}
-                    pairs = value.split(',')
-                    for pair in pairs
-                        [key, val] = pair.split(':')
-                        key = key.trim()
-                        val = val.trim()
-                        console.log("key: #{key}\nval: #{val}")
-                        if key == 'name'
-                            params.componentName = val
-                        else
-                            params.opts[key] = val
-                    console.log('Component:')
-                    console.log(params)
-                    wrapper.components.push(params)
-                    wrapper.emit('createComponent', params)
-                return true
-
-    methods = ['setAttributeNode', 'setAttributeNodeNS',
-               'removeAttributeNode', 'removeAttributeNodeNS']
-    for method in methods
-        do (method) ->
-            wrapMethod(dom.Element.prototype, method, (elem, args, rv) ->
-                tagName = elem.tagName.toLowerCase()
-                attr = args[0] # Attribute node
-                if tagName == 'iframe' || tagName == 'frame'
-                    if attr.name.toLowerCase() == 'src'
-                        return false
-                return true
-            )
-
-    for method in ['setAttribute', 'setAttributeNS']
-        do (method) ->
-            wrapMethod(dom.HTMLFrameElement.prototype, method, (elem, args, rv) ->
-                tagName = elem.tagName.toLowerCase()
-                attr = if /NS$/.test(method)
-                    args[1].toLowerCase()
-                else
-                    args[0].toLowerCase()
-                # If src attribute is set, jsdom will create/load a new document.
-                if attr == 'src'
-                    # We need to wait for load because that means that the old
-                    # Document has been deleted, and the new one has been
-                    # created.
-                    elem.addEventListener('load', () ->
-                        # At this point, JSDOM has deleted the old document
-                        # and created the new one, so we can (and should) go
-                        # ahead and tag it.
-                        wrapper.nodes.add(elem.contentDocument)
-                        # Next, we need to clean out the client's iframe, since
-                        # setting src creates a new iframe on the server.
-                        wrapper.emit('clear', frame : elem.__nodeID)
-                        # Then tag the document with its new ID.
-                        wrapper.emit('tagDocument',
-                            parent : elem.__nodeID
-                            id : elem.contentDocument.__nodeID
-                        )
-                    )
-                return false
-            )
-
-    methods = ['createTextNode', 'createAttribute', 'createDocumentFragment',
-               'createComment', 'createCDATASection', 'importNode',
-               'createAttributeNS']
-    for method in methods
-        wrapMethod(dom.Document.prototype, method)
-
-    for method in ['createElement', 'createElementNS']
-        do (method) ->
-            wrapMethod(dom.Document.prototype, method, (elem, args, rv) ->
-                tagName =
-                    if /NS$/.test(method)
-                        args[1].toLowerCase()
-                    else
-                        args[0].toLowerCase()
-                if tagName == 'iframe' || tagName == 'frame'
-                    # Note: this Document will be discarded if src is set, but
-                    # we still need to support iframes that are created
-                    # programatically.
-                    wrapper.nodes.add(rv.contentDocument)
-                    # We need to emit the DOMUpdate before we can tag the
-                    # iframe's document.  We have to do it here since we can't
-                    # tell the caller to tag the document after, and we don't
-                    # want to add the special case check to every single
-                    # method invocation by putting it in the advice.
-                    wrapper.emit('DOMUpdate'
-                        targetID : elem.__nodeID
-                        rvID : rv.__nodeID
-                        method : method
-                        args : wrapper.nodes.scrub(args))
-                    wrapper.emit('tagDocument',
-                        parent : rv.__nodeID
-                        id : rv.contentDocument.__nodeID)
-                    # We return false because we already emitted the instruction.
-                    return false
-                return true
-            )
-
-    for method in ['appendData', 'insertData', 'deleteData', 'replaceData']
-        wrapMethod(dom.CharacterData.prototype, method)
-    wrapProperty(dom.CharacterData.prototype, 'data')
-    wrapProperty(dom.Attr.prototype, 'value')
-    wrapMethod(dom.Text.prototype, 'splitText')
-    wrapProperty(dom.Text.prototype, 'value')
-
-    # w3c Level 2 HTML
-    # Note: most of the properties on HTML elements are implemented with
-    # setters that call setAttribute behind the scenes, so we don't have
-    # to wrap them.
-    for property in ['title', 'cookie']
-        wrapProperty(dom.HTMLDocument.prototype, property)
-    wrapProperty(dom.HTMLTitleElement.prototype, 'text')
-    wrapProperty(dom.HTMLMetaElement.prototype, 'httpEquiv')
-    wrapProperty(dom.HTMLIsIndexElement.prototype, 'prompt')
-    for method in ['submit', 'reset']
-        wrapMethod(dom.HTMLFormElement.prototype, method)
-    for method in  ['add', 'remove', 'blur', 'focus']
-        wrapMethod(dom.HTMLSelectElement.prototype, method)
-    for property in ['selectedIndex', 'value']
-        wrapProperty(dom.HTMLSelectElement.prototype, property)
-    for property in ['selected']
-        wrapProperty(dom.HTMLOptionElement.prototype, property)
-
-    for method in ['blur', 'focus', 'select']
-        wrapMethod(dom.HTMLTextAreaElement.prototype, method)
-    for method in ['blur', 'focus']
-        wrapMethod(dom.HTMLAnchorElement.prototype, method)
-    methods = ['createTHead', 'deleteTHead', 'createTFoot', 'deleteTFoot',
-               'createCaption', 'deleteCaption', 'insertRow', 'deleteRow']
-    for method in methods
-        wrapMethod(dom.HTMLTableElement.prototype, method)
-    for method in ['insertRow', 'deleteRow']
-        wrapMethod(dom.HTMLTableSectionElement.prototype, method)
-    for method in ['insertCell', 'deleteCell']
-        wrapMethod(dom.HTMLTableRowElement.prototype, method)
-    wrapProperty(dom.HTMLTableCellElement.prototype, 'headers')
-
-    wrapProperty(dom.HTMLInputElement.prototype, 'checked')
-    wrapProperty(dom.HTMLInputElement.prototype, 'value')
-
-
-exports.wrapStyle = (dom, wrapper) ->
-    # JSDOM level2/style.js uses the style getter to lazily create the
-    # CSSStyleDeclaration object for the element.  To be able to emit
-    # the right instruction in the style object advice, we need to have
-    # a pointer to the element that owns the style object, so we create it
-    # here.
-    do () ->
-        proto = dom.HTMLElement.prototype
-        getter = proto.__lookupGetter__('style')
-        proto.__defineGetter__ 'style', () ->
-            style = getter.call(this)
-            style._parentElement = this
-            return style
-
-    # This list is from:
-    #   http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
-    cssAttrs = [
-        'azimuth', 'background', 'backgroundAttachment', 'backgroundColor',
-        'backgroundImage', 'backgroundPosition', 'backgroundRepeat', 'border',
-        'borderCollapse', 'borderColor', 'borderSpacing', 'borderStyle',
-        'borderTop', 'borderRight', 'borderBottom', 'borderLeft',
-        'borderTopColor', 'borderRightColor', 'borderBottomColor',
-        'borderLeftColor', 'borderTopStyle', 'borderRightStyle',
-        'borderBottomStyle', 'borderLeftStyle', 'borderTopWidth',
-        'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth',
-        'borderWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color',
-        'content', 'counterIncrement', 'counterReset', 'cue', 'cueAfter',
-        'cueBefore', 'cursor', 'direction', 'display', 'elevation',
-        'emptyCells', 'cssFloat', 'font', 'fontFamily', 'fontSize',
-        'fontSizeAdjust', 'fontStretch', 'fontStyle', 'fontVariant',
-        'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
-        'listStyle', 'listStyleImage', 'listStylePosition', 'listStyleType',
-        'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
-        'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight',
-        'minWidth', 'orphans', 'outline', 'outlineColor', 'outlineStyle',
-        'outlineWidth', 'overflow', 'padding', 'paddingTop', 'paddingRight',
-        'paddingBottom', 'paddingLeft', 'page', 'pageBreakAfter',
-        'pageBreakBefore', 'pageBreakInside', 'pause', 'pauseAfter',
-        'pauseBefore', 'pitch', 'pitchRange', 'playDuring', 'position',
-        'quotes', 'richness', 'right', 'size', 'speak', 'speakHeader',
-        'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
-        'tableLayout', 'textAlign', 'textDecoration', 'textIndent',
-        'textShadow', 'textTransform', 'top', 'unicodeBidi', 'verticalAlign',
-        'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', 'width',
-        'wordSpacing', 'zIndex'
-    ]
-
-    # For each possible style property, add a wrapper to emit advice.
-    do () ->
-        proto = dom.CSSStyleDeclaration.prototype
-        cssAttrs.forEach (attr) ->
-            proto.__defineSetter__ attr, (val) ->
-                # cssom seems to use some CSSStyleDeclaration objects
-                # internally, so we only want to emit instructions if there
-                # is a parent element pointer, meaning this CSSStyleDeclaration
-                # belongs to an element.
-                if this._parentElement
-                    wrapper.emit 'DOMPropertyUpdate',
-                        targetID : this._parentElement.__nodeID
-                        style : true
-                        prop : attr
-                        value : val
-                return @["_#{attr}"] = val
-            proto.__defineGetter__ attr, () ->
-                return @["_#{attr}"]
-
-    # TODO:
-    # JSDOM uses CSSOM's parse() method to turn text into CSSStyleSheet
-    # objects.  We need to replace the current @import rules, which use
-    # non-ResourceProxy'd URLs, with ResourceProxy URLs.
-    # The ResourceProxy should return the modified CSS sheet instead of
-    # fetching from source.


CORS foolishness now not needed

CORS foolishness now not needed

--- code/serv.coffee
+++ code/serv.coffee
@@ -38,11 +38,6 @@

 # Templating language
 app.set('view engine', 'ejs')
-
-# Allow Cross Origin Resource Sharing for all requests
-app.all '/*', (req, res, next) ->
-  res.header("Access-Control-Allow-Origin", "*")
-  next()

 # GET REQUESTS
 # These should all be idempotent, i.e. make no changes to the server.


Clear countdown before returning from reconnect

--- services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -105,9 +105,9 @@

 		tryReconnect: () ->
 			@cancelReconnect()
+			delete @$scope.connection.reconnection_countdown
 			return if @connected
 			@$scope.connection.reconnecting = true
-			delete @$scope.connection.reconnection_countdown
 			@ide.socket.socket.reconnect()
 			setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000


test `untrackSession`

test `untrackSession`

--- services/web/test/UnitTests/coffee/User/UserSessionsManager.coffee
+++ services/web/test/UnitTests/coffee/User/UserSessionsManager.coffee
@@ -148,3 +148,97 @@
 				@call (err) =>
 					@_checkSessions.callCount.should.equal 0
 					done()
+
+	describe 'untrackSession', ->
+
+		beforeEach ->
+			@call = (callback) =>
+				@UserSessionsManager.untrackSession @user, @sessionId, callback
+			@rclient.exec.callsArgWith(0, null)
+			@_checkSessions = sinon.stub(@UserSessionsManager, '_checkSessions').returns(null)
+
+		afterEach ->
+			@_checkSessions.restore()
+
+		it 'should not produce an error', (done) ->
+			@call (err) =>
+				expect(err).to.not.be.instanceof Error
+				done()
+
+		it 'should call the appropriate redis methods', (done) ->
+			@call (err) =>
+				@rclient.multi.callCount.should.equal 1
+				@rclient.srem.callCount.should.equal 1
+				@rclient.expire.callCount.should.equal 1
+				@rclient.exec.callCount.should.equal 1
+				done()
+
+		it 'should call _checkSessions', (done) ->
+			@call (err) =>
+				@_checkSessions.callCount.should.equal 1
+				done()
+
+		describe 'when rclient produces an error', ->
+
+			beforeEach ->
+				@rclient.exec.callsArgWith(0, new Error('woops'))
+
+			it 'should produce an error', (done) ->
+				@call (err) =>
+					expect(err).to.be.instanceof Error
+					done()
+
+			it 'should not call _checkSessions', (done) ->
+				@call (err) =>
+					@_checkSessions.callCount.should.equal 0
+					done()
+
+		describe 'when no user is supplied', ->
+
+			beforeEach ->
+				@call = (callback) =>
+					@UserSessionsManager.untrackSession null, @sessionId, callback
+
+			it 'should not produce an error', (done) ->
+				@call (err) =>
+					expect(err).to.not.be.instanceof Error
+					expect(err).to.equal null
+					done()
+
+			it 'should not call the appropriate redis methods', (done) ->
+				@call (err) =>
+					@rclient.multi.callCount.should.equal 0
+					@rclient.srem.callCount.should.equal 0
+					@rclient.expire.callCount.should.equal 0
+					@rclient.exec.callCount.should.equal 0
+					done()
+
+			it 'should not call _checkSessions', (done) ->
+				@call (err) =>
+					@_checkSessions.callCount.should.equal 0
+					done()
+
+		describe 'when no sessionId is supplied', ->
+
+			beforeEach ->
+				@call = (callback) =>
+					@UserSessionsManager.untrackSession @user, null, callback
+
+			it 'should not produce an error', (done) ->
+				@call (err) =>
+					expect(err).to.not.be.instanceof Error
+					expect(err).to.equal null
+					done()
+
+			it 'should not call the appropriate redis methods', (done) ->
+				@call (err) =>
+					@rclient.multi.callCount.should.equal 0
+					@rclient.srem.callCount.should.equal 0
+					@rclient.expire.callCount.should.equal 0
+					@rclient.exec.callCount.should.equal 0
+					done()
+
+			it 'should not call _checkSessions', (done) ->
+				@call (err) =>
+					@_checkSessions.callCount.should.equal 0
+					done()


fix: Remove debug message

fix: Remove debug message

--- src/grapp-rest-resource.coffee
+++ src/grapp-rest-resource.coffee
@@ -111,5 +111,4 @@
     h = {'Accept': 'application/json'}
     for key, val of @headers
       h[key] = val
-    console.log h
     h


bugfix when slicing is disabled

bugfix when slicing is disabled

--- packages/sha256/client/crypto.coffee
+++ packages/sha256/client/crypto.coffee
@@ -138,6 +138,8 @@
       end = @totalSize
     @chunkStart = end
     if @disableSlicing
+      # move chunkStart to the end because we send the whole chunk at once
+      @chunkStart = @totalSize
       chunk = @current.data
       transfer = @current.transfer
     else


better go mode

better go mode

--- client/specs/go.coffee
+++ client/specs/go.coffee
@@ -25,4 +25,7 @@
         "complex128", "byte", "rune"
     ]
     KEY3: [
+        "true", "false", "iota", "nil",
+        "append", "cap", "close", "complex", "copy", "delete", "imag", "len",
+        "make", "new", "panic", "print", "println", "real", "recover"
     ]


Fix playmode

Fix playmode

--- resources/assets/coffee/react/beatmapset-page/scoreboard.coffee
+++ resources/assets/coffee/react/beatmapset-page/scoreboard.coffee
@@ -44,7 +44,7 @@
     modsClassName = 'beatmapset-scoreboard__mods'
     modsClassName += ' beatmapset-scoreboard__mods--initial' if _.isEmpty @props.enabledMods

-    mods = if @props.playmode == 'mania'
+    mods = if @props.beatmap.mode == 'mania'
       ['NM', '4K', '5K', '6K', '7K', '8K', '9K', 'EZ', 'NF', 'HT', 'HR', 'SD', 'PF', 'DT', 'NC', 'FI', 'HD', 'FL']
     else
       ['NM', 'EZ', 'NF', 'HT', 'HR', 'SD', 'PF', 'DT', 'NC', 'HD', 'FL']
@@ -101,5 +101,5 @@
       score: score
       position: rank
       key: rank
-      playmode: @props.playmode
+      playmode: @props.beatmap.mode
       countries: @props.countries


Image smoothing

Image smoothing

--- app/scripts/Core.coffee
+++ app/scripts/Core.coffee
@@ -4,13 +4,18 @@
     running: false
     debug: false # This will log a ton of data to the console if set to true

-    imageSmoothing: false
+    imageSmoothing: true

     stats: new Stats()

     constructor: (@canvas) ->
         @context = @canvas.getContext('2d')
         @context.imageSmoothingEnabled = @imageSmoothing
+
+        if @context.webkitImageSmoothingEnabled
+            @context.webkitImageSmoothingEnabled = @imageSmoothing;
+        if @context.mozImageSmoothingEnabled
+            @context.mozImageSmoothingEnabled = @imageSmoothing;

         # The current view contains everything specific to the current game "stage",
         # such as player or AI entities, the current keyboard handler, etc.


remove comments

remove comments

--- app/scripts/main.coffee
+++ app/scripts/main.coffee
@@ -7,16 +7,13 @@

 @Clean.editor = new Editor
   askid: (editor)->
-    # status.connecting = true
     editor.setConnecting true
     $.ajax
     url: 'http://localhost:3000'
     success: (data)->
       console.log 'ask id success'
       editor.id = 'MOCKID123'
-      # editor.status.connecting = false
       editor.setConnecting false
     error: ->
       console.log 'unable to ask id'
-      # editor.status.connecting = false
       editor.setConnecting false


Removed unnecessary plugin call

Removed unnecessary plugin call

--- app/scripts/main.coffee
+++ app/scripts/main.coffee
@@ -1,2 +1,2 @@
 $ ->
-  $('#example_input').multinput()
+


koding-worker: switch to using local hostname due to bug in getting amazon host name

--- node_modules_koding/koding-worker/main.coffee
+++ node_modules_koding/koding-worker/main.coffee
@@ -75,14 +75,8 @@
     version = (fs.readFileSync nodePath.join(__dirname, '../../VERSION'), 'utf-8').trim()

   getHostName:() ->
-    if fs.existsSync("/usr/bin/ec2metadata")
-      console.log "Using ip from EC2 ..."
-      res = @execSync("ec2metadata --public-ipv4")
-      hostName = res.trim() + "-" + @getVersion()
-    else
-      console.log "Using local hostname ..."
-      # hostName = os.hostname() + "-" + @getVersion()
-      hostName = os.hostname()
+    console.log "Using local hostname ..."
+    hostName = os.hostname() + "-" + @getVersion()

   execSync : (cmd) ->
     exec cmd + " > kontrolExecOutput; echo done > kontrolExecSync"


Add User auth

Add User auth

--- coffee/BabyFtpd.coffee
+++ coffee/BabyFtpd.coffee
@@ -5,8 +5,12 @@
 net = require("net")

 module.exports = class BabyFtpd
+  authUser = {}
   constructor: (option = {})->
-    @piServer  = undefined
+    @piServer = undefined
+
+  addUser: (userName, userPass)->
+    authUser[userName] = userPass

   listen: (port = 21, host = "0.0.0.0")->
     @piServer = net.createServer()
@@ -21,6 +25,7 @@
       socket.setNoDelay()
       socket.dataEncoding = "binary"
       socket.passive = false
+      socket.userName = null

       # Socket response
       socket.reply = (status, message, callback)->
@@ -154,18 +159,21 @@
       @reply 202

     # 仮実装しているもの
-    #"USER": (username)->
-    #"PASS": (password)->
     #"RETR": ()->
     #"PWD": ()->
     #"NLST": ()->

     # 各コマンドの処理
     "USER": (username)->
+      @userName = username
       @reply 331

     "PASS": (password)->
-      @reply 230
+      userPass = authUser[@userName]
+      if userPass? and userPass is password
+        @reply 230
+      else
+        @reply 530

     "PWD": ()->
       @reply 257, '"/"'
@@ -245,4 +253,5 @@

 unless module.parent
   ftpd = new BabyFtpd
+  ftpd.addUser "taka", "1234"
   ftpd.listen 8021, "localhost"


Make the DB all lovely and require.js

Make the DB all lovely and require.js

--- coffeescript/db.coffee
+++ coffeescript/db.coffee
@@ -1,15 +1,14 @@
 fs = require 'fs'
 path = require 'path'

-generateUid = ->
-	s4 = ->
-		(((1+Math.random())*0x10000)|0).toString(16).substring(1)
+class noteddb
+	constructor: (@notebookdir) ->
+
+	generateUid: ->
+		s4 = ->
+			(((1+Math.random())*0x10000)|0).toString(16).substring(1)

-	(s4() + s4() + s4() + s4()).toLowerCase()
-
-
-class db
-	constructor: (@notebookdir) ->
+		(s4() + s4() + s4() + s4()).toLowerCase()

 	###
 	# Finds the filename of a particular note id
@@ -33,11 +32,11 @@
 	# @return {String} id The new notebook id
 	###
 	createNotebook: (name) ->
-		id = generateUid()
+		id = @generateUid()

 		# there's a 16^16 chance of conflicting, but hey
 		while fs.existsSync(path.join(@notebookdir, id  + ".json"))
-			id = generateUid()
+			id = @generateUid()

 		fs.writeFile path.join(@notebookdir, id + ".json"),
 			JSON.stringify {
@@ -54,10 +53,10 @@
 	# @return {String} id The new note id
 	###
 	createNote: (name, notebook, content) ->
-		id = generateUid()
+		id = @generateUid()

 		while fs.existsSync(path.join(@notebookdir, notebook + "." + id  + ".noted"))
-			id = generateUid()
+			id = @generateUid()

 		fs.writeFile path.join(@notebookdir, notebook + "." + id  + ".noted"),
 			JSON.stringify {
@@ -170,3 +169,5 @@
 	###
 	deleteNote: (id) ->
 		fs.unlink path.join(@notebookdir, @filenameNote(id))
+
+module.exports = noteddb


Fixed defaults snippet comment

Fixed defaults snippet comment

--- completions.cson
+++ completions.cson
@@ -884,7 +884,7 @@
   'definitions': {
     'comment': """
     /**
-     * Belongs_to relationship
+     * Add default values to database
      * @var array
      */
     """


Even more better UX around scrolling.

Even more better UX around scrolling.

--- foreground.coffee
+++ foreground.coffee
@@ -47,7 +47,6 @@

   performUpDown: (action, event) ->
     elements = @getElements action
-    elements.reverse() if action == "up"

     oldIndex = elements.indexOf @element
     newIndex = Math.max 0, Math.min elements.length - 1, oldIndex + 1
@@ -108,6 +107,8 @@
       prev = curr
       not duplicate

+    if action == "up" then elements.reverse() else elements
+
   getPosition: (element) ->
     { top, bottom } = element.getBoundingClientRect()
     if top <= 0
@@ -205,18 +206,21 @@
   onScroll: do ->
     delay = 50
     timer = null
+    previousPageYOffset = pageYOffset

     cancel = ->
       if timer
         clearTimeout timer; timer = null

     (event) ->
+      direction = if previousPageYOffset < pageYOffset then "down" else "up"
+      previousPageYOffset = pageYOffset
       cancel()
       timer = Common.setTimeout delay, =>
         timer = null
         if @element and not Common.isInViewport @element
           @clearSelection()
-        if not @element and element = @getElements("down")[0]
+        if not @element and element = @getElements(direction)[0] and Common.isInViewport element
           @selectElement element, false

 Wrapper =


let set custom namespace for some methods

--- vendor/assets/javascripts/angular_objectify_resource/resource_extension.coffee
+++ vendor/assets/javascripts/angular_objectify_resource/resource_extension.coffee
@@ -15,10 +15,10 @@
           else
             resource

-      build_nested_collection: (callback)->
+      build_nested_collection: (callback, special_options = {})->
         that = @
         (response)->
-          namespace  = options.namespace
+          namespace  = special_options.namespace || options.namespace
           isArray    = angular.isArray(response)
           collection = if isArray then response else response[namespace]
           resources  = _.map collection, that.resource_build_method


Changed color of speed-sign text from undefined to black

--- dev/standard.cson
+++ dev/standard.cson
@@ -71,7 +71,7 @@
     elements: [
       { type: 'circle-bg', value: 'white' }
       { type: 'circle-o', value: 'red' }
-      { type: 'content', value: 'undefined' }
+      { type: 'content', value: 'black' }
     ]

   mandatory_pedestrian_cycle_dual_track:


update with additional step events for parent to hook into later

--- src/exercise/collection.coffee
+++ src/exercise/collection.coffee
@@ -59,8 +59,11 @@

     onStepCompleted: ->
       console.info('onStepCompleted')
+      channel.emit("completed.#{stepId}")
+
     onNextStep: ->
       console.info('onNextStep')
+      channel.emit("leave.#{stepId}")

 api.channel.on("exercise.*.receive.*", update)


improve first name parser by adding honorific exclusions (#2732)

--- src/flux/models/contact.coffee
+++ src/flux/models/contact.coffee
@@ -146,9 +146,9 @@
     return @_nameParts().join(' ')

   firstName: ->
-    articles = ['a', 'the']
+    exclusions = ['a', 'the', 'dr.', 'mrs.', 'mr.', 'mx.', 'prof.', 'ph.d.']
     for part in @_nameParts()
-      if part.toLowerCase() not in articles
+      if part.toLowerCase() not in exclusions
         return part
     return ""


rename every input to drawevery

rename every input to drawevery

--- components/Draw.coffee
+++ components/Draw.coffee
@@ -4,7 +4,7 @@
   description: 'Draws received drawing commands'
   icon: 'pencil'
   constructor: ->
-    @every = false
+    @drawevery = false
     @clearevery = false
     @canvas = null
     @context = null
@@ -12,7 +12,7 @@

     @inPorts =
       tick: new noflo.Port 'bang'
-      every: new noflo.Port 'boolean'
+      drawevery: new noflo.Port 'boolean'
       clearevery: new noflo.Port 'boolean'
       canvas: new noflo.Port 'object'
       commands: new noflo.ArrayPort 'object'
@@ -28,8 +28,8 @@
         loglevel: 'error'
         message: 'Received commands but there is not 2d context attached.'

-    @inPorts.every.on 'data', (data) =>
-      @every = data
+    @inPorts.drawevery.on 'data', (data) =>
+      @drawevery = data

     @inPorts.clearevery.on 'data', (data) =>
       @clearevery = data
@@ -40,13 +40,13 @@

     @inPorts.commands.on 'data', (commands, i) =>
       @commands[i] = commands
-      if @every
+      if @drawevery
         @parse @commands

     # @inPorts.commands.on 'detach', (i) =>
     #   @commands.splice i, 1
-    #   if @every
+    #   if @drawevery
     #     @parse @commands


Hide exact match value

Hide exact match value

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -206,11 +206,11 @@
           else
             <button type="button" onClick={@toggleExpertClassification.bind this, true}>Show</button>}

-          {unless isNaN @state.classificationQuality
+          {unless true or isNaN @state.classificationQuality
             qualityString = (@state.classificationQuality * 100).toString().split('.')[0] + '%'

Looks like you matched about {qualityString}.
}
           {if @state.classificationQuality < @props.goodClassificationCutoff
-
Keep at it, all classifications are useful!!
}
+
Keep at it, all classifications are useful!
}
           {if @state.classificationQuality > @props.goodClassificationCutoff

Keep up the good work!
}

}


Advance classifier to next image after navigating

..to Talk

Closes #1239

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -122,7 +122,7 @@

       <nav className="task-nav">
         {if @props.owner? and @props.project?
-          <Link to="project-talk-subject" params={owner: @props.owner.login, name: @props.project.slug, id: @props.subject.id} className="talk standard-button">Talk</Link>}
+          <Link onClick={@props.onClickNext} to="project-talk-subject" params={owner: @props.owner.login, name: @props.project.slug, id: @props.subject.id} className="talk standard-button">Talk</Link>}
         <button type="button" className="continue major-button" onClick={@props.onClickNext}>Next</button>
       </nav>
     </div>


Rename cleanUrl to api:url:clean

Rename cleanUrl to api:url:clean

--- lib/appBaseComponents/entities/Collection.coffee
+++ lib/appBaseComponents/entities/Collection.coffee
@@ -69,7 +69,7 @@

     # overwrite the url with the one received from the API
     if url
-      normalizedUrl = channel.request 'cleanUrl', url
+      normalizedUrl = channel.request 'api:url:clean', url
       @url = normalizedUrl or url

     if paginator


reflexion required...

reflexion required...

--- lib/assets/javascripts/jax/input/mouse.js.coffee
+++ lib/assets/javascripts/jax/input/mouse.js.coffee
@@ -101,7 +101,7 @@
         super 'over'
         super 'exit'
         @addEventListener 'enter', callback if callback
-      when 'move', 'click', 'wheel'
+      when 'move', 'click'
         super 'move'
         super 'press'
         super 'release'


update patterns

update patterns


Better check if deletion allowed

Better check if deletion allowed

--- routes.coffee
+++ routes.coffee
@@ -70,15 +70,11 @@
         thread: thread

   app.get '/thread/:threadId/remove', loggedIn, (req, res) ->
-    Thread.findById req.params.threadId, (err, thread) ->
-      # Define callback do keep it DRY
-      callback = (err, thread) ->
-        res.redirect "/"
-
-      if thread.creator.toString() is req.user._id.toString()
-        thread.remove callback
-      else
-        callback null, thread
+    Thread.findOneAndRemove
+       _id: req.params.threadId
+       creator: req.user._id
+    , (err, thread) ->
+      res.redirect "/"

   # Posts
   app.post '/thread/:threadId/post/new', loggedIn, (req, res) ->
@@ -95,12 +91,8 @@
         res.redirect "/thread/#{thread.id}"

   app.get '/thread/:threadId/post/:postId/remove', loggedIn, (req, res) ->
-    Post.findById req.params.postId, (err, post) ->
-      # Define callback do keep it DRY
-      callback = (err, post) ->
-        res.redirect "/thread/#{req.params.threadId}"
-
-      if post.author.toString() is req.user._id.toString()
-        post.remove callback
-      else
-        callback null, post
+    Post.findOneAndRemove
+       _id: req.params.postId
+       author: req.user._id
+    , (err, thread) ->
+      res.redirect "/thread/#{req.params.threadId}"


Only create AMD modules for now until we can distribute CJS modules

--- grunt/6to5.coffee
+++ grunt/6to5.coffee
@@ -3,7 +3,7 @@
     sourceMap: true
   dist:
     options:
-      modules: 'umd'
+      modules: 'amd'
     files: [{
       expand: true,
       cwd: 'src',


excluding test from being crawled too

excluding test from being crawled too

--- grunt/exec.coffee
+++ grunt/exec.coffee
@@ -1,6 +1,6 @@
 module.exports = (grunt) ->
   grunt.config 'exec',
     crawl:
-      cmd: 'phantomjs crawler.coffee http://localhost:8000/ build/ 3000 "docs"'
+      cmd: 'phantomjs crawler.coffee http://localhost:8000/ build/ 3000 "docs|test"'

   grunt.loadNpmTasks 'grunt-exec'


Add kadmin instruction for renewable tickets

--- actions/krb5_client.coffee
+++ actions/krb5_client.coffee
@@ -15,6 +15,10 @@
     SASL GSSAPI OpenLDAP authentication.
     Use SAS:L GSSAPI Authentication with AutoFS.
 IMPORTANT : Kerberos clients require connectivity to the KDC's TCP ports 88 and 749.
+
+You haven't renewable tickets, this is per default disallowed in the most linux distributions. This can be done per:
+kadmin.local: modprinc -maxrenewlife 7day krbtgt/YOUR_REALM
+kadmin.local: modprinc -maxrenewlife 7day +allow_renewable hue/FQRN

 ###


Prevent message forced refresh when removing room

--- packages/rocketchat-ui/lib/RoomManager.coffee
+++ packages/rocketchat-ui/lib/RoomManager.coffee
@@ -21,13 +21,14 @@
 Meteor.startup ->
 	ChatMessage.find().observe
 		removed: (record) ->
-			recordBefore = ChatMessage.findOne {ts: {$lt: record.ts}}, {sort: {ts: -1}}
-			if recordBefore?
-				ChatMessage.update {_id: recordBefore._id}, {$set: {tick: new Date}}
-
-			recordAfter = ChatMessage.findOne {ts: {$gt: record.ts}}, {sort: {ts: 1}}
-			if recordAfter?
-				ChatMessage.update {_id: recordAfter._id}, {$set: {tick: new Date}}
+			if RoomManager.getOpenedRoomByRid(record.rid)?
+				recordBefore = ChatMessage.findOne {ts: {$lt: record.ts}}, {sort: {ts: -1}}
+				if recordBefore?
+					ChatMessage.update {_id: recordBefore._id}, {$set: {tick: new Date}}
+
+				recordAfter = ChatMessage.findOne {ts: {$gt: record.ts}}, {sort: {ts: 1}}
+				if recordAfter?
+					ChatMessage.update {_id: recordAfter._id}, {$set: {tick: new Date}}

 onDeleteMessageStream = (msg) ->
@@ -148,6 +149,11 @@
 				Dep.depend()
 				return openedRooms[typeName].ready
 		}
+
+	getOpenedRoomByRid = (rid) ->
+		for typeName, openedRoom of openedRooms
+			if openedRoom.rid is rid
+				return openedRoom

 	getDomOfRoom = (typeName, rid) ->
 		room = openedRooms[typeName]
@@ -205,3 +211,4 @@
 	updateUserStatus: updateUserStatus
 	onlineUsers: onlineUsers
 	updateMentionsMarksOfRoom: updateMentionsMarksOfRoom
+	getOpenedRoomByRid: getOpenedRoomByRid


Improved selection of content editable file.

--- app/assets/javascripts/app/controllers/widget/text_module.js.coffee
+++ app/assets/javascripts/app/controllers/widget/text_module.js.coffee
@@ -7,7 +7,10 @@
     if @selector
       @bindElements = @$( @selector ).textmodule()
     else
-      @bindElements = @$('[contenteditable]').textmodule()
+      if @el.attr('contenteditable')
+        @bindElements = @el.textmodule()
+      else
+        @bindElements = @$('[contenteditable]').textmodule()
     @update()

     @subscribeId = App.TextModule.subscribe(@update, initFetch: true )


Fix issue with term on cloned course (Closes #419)

Because of the misspelling of the React component method, the cloned
status of the course was being set to `undefined`, so it was nulling out
the course term in the jbuilder, which caused it not to be set on
rendering. Subsequent edits, then, would likely cause the term to be
null and cause all kinds of fun errors. This commit fixes that issue.

--- app/assets/javascripts/components/overview/course_cloned_modal.cjsx
+++ app/assets/javascripts/components/overview/course_cloned_modal.cjsx
@@ -34,7 +34,7 @@
     @setState valuesUpdated: true
   cloneCompletedStatus: 2
   saveCourse: ->
-    @updateCourse('cloned_status', @clonedCompletedStatus)
+    @updateCourse('cloned_status', @cloneCompletedStatus)
     if ValidationStore.isValid()
       @setState isSubmitting: true
       ValidationActions.setInvalid 'exists', 'This course is being checked for uniqueness', true


#167326748 case for edit working

#167326748 case for edit working

--- app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
@@ -260,7 +260,7 @@
     maxPhotos = $(this).find('#max-photos').attr("data-max-photos")
     if maxPhotos
       addedPhotos = $(this).find('.gallery-item').find("img").length
-      console.log(maxPhotos + ' ' + addedPhotos)
+      # console.log(maxPhotos + ' ' + addedPhotos)
       checkMaxPhotos(this, maxPhotos, addedPhotos)
   )

@@ -268,7 +268,8 @@
     maxPhotos = $(this).find('#max-photos').attr("data-max-photos")
     if maxPhotos
       $(this).bind 'cloudinarydone', (e) ->
-        addedPhotos = $(this).find('.photo-preview').find("img").length
+        addedPhotos = $(this).find('.gallery-item').find("img").length
+        addedPhotos += $(this).find('.photo-preview').find("img").length
         checkMaxPhotos(this, maxPhotos, addedPhotos)

   $('.panel-body').on 'click', '.remove-upload', (e) ->


tweak error message to match mockup

tweak error message to match mockup

--- app/assets/javascripts/views/overlays/figure_overlay_view.js.coffee
+++ app/assets/javascripts/views/overlays/figure_overlay_view.js.coffee
@@ -22,7 +22,7 @@
         progressBarStyle: "width: 0%;"

       if file.error
-        upload.error = "File #{file.name} is an incorrect format. You may upload .jpg, .jpeg, .gif, .png, or .tiff."
+        upload.error = "File #{file.name} is not an image. You may upload files with the extension .jpg, .jpeg, .gif, .png, or .tiff."

       @uploads.pushObject upload


Prepend “Title: “ to figure titles when first uploaded

--- app/assets/javascripts/views/overlays/figure_overlay_view.js.coffee
+++ app/assets/javascripts/views/overlays/figure_overlay_view.js.coffee
@@ -43,6 +43,7 @@

       store = @get('controller.store')
       updatedFigures = _.map data.result.figures, (figure) ->
+        figure.title = "Title: " + figure.title
         store.push 'figure', figure

       @get('figures').pushObjects updatedFigures


JCredential:clone: we now can clone a credential and create exact same copy with JCredentialData

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -43,6 +43,8 @@
       instance        :
         delete        :
           (signature Function)
+        clone         :
+          (signature Function)
         shareWith     :
           (signature Object, Function)
         fetchUsers    :
@@ -330,6 +332,32 @@

           rel.remove callback

+
+  clone: permit
+
+    advanced: [
+      { permission: 'modify credential' }
+      { permission: 'update credential', validateWith: Validators.own }
+    ]
+
+    success: (client, callback) ->
+
+      { delegate } = client.connection
+
+      cloneData  =
+        title    : @getAt 'title'
+        provider : @getAt 'provider'
+
+      @fetchData (err, data) ->
+
+        return callback err  if err
+
+        cloneData.meta = data.meta  if data?.meta
+        JCredential.create client, cloneData, callback
+
+      , shadowSensitiveData = no
+
+
   # Poor man's shadow function ~ GG
   shadowed = (c) ->
     return  unless c


Fifth batch of Swedish translations. (#6127)

--- app/locale/sv.coco.coffee
+++ app/locale/sv.coco.coffee
@@ -885,133 +885,133 @@
     prompt_body: "Vill du skaffa flera?"
     prompt_button: "Gå till affären"
     recovered: "Föregående köp av ädelstenar återskapat. Var vänlig och uppdatera sidan."
-#    price: "x{{gems}} / mo"
-#    buy_premium: "Buy Premium"
-#    purchase: "Purchase"
-#    purchased: "Purchased"
-
-#  subscribe_for_gems:
-#    prompt_title: "Not Enough Gems!"
-#    prompt_body: "Subscribe to Premium to get gems and access to even more levels!"
-
-#  earn_gems:
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Keep playing to earn more!"
-
-#  subscribe:
-#    best_deal: "Best Deal!"
-#    confirmation: "Congratulations! You now have a CodeCombat Premium Subscription!"
-#    premium_already_subscribed: "You're already subscribed to Premium!"
-#    subscribe_modal_title: "CodeCombat Premium"
-#    comparison_blurb: "Become a Master Coder - subscribe to Premium today!"
-#    must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
-#    subscribe_title: "Subscribe" # Actually used in subscribe buttons, too
-#    unsubscribe: "Unsubscribe"
-#    confirm_unsubscribe: "Confirm Unsubscribe"
-#    never_mind: "Never Mind, I Still Love You"
-#    thank_you_months_prefix: "Thank you for supporting us these last"
-#    thank_you_months_suffix: "months."
-#    thank_you: "Thank you for supporting CodeCombat."
-#    sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
-#    unsubscribe_feedback_placeholder: "O, what have we done?"
-#    stripe_description: "Monthly Subscription"
-#    stripe_yearly_description: "Annual Subscription"
-#    buy_now: "Buy Now"
-#    subscription_required_to_play: "You'll need a subscription to play this level."
-#    unlock_help_videos: "Subscribe to unlock all video tutorials."
-#    personal_sub: "Personal Subscription" # Accounts Subscription View below
-#    loading_info: "Loading subscription information..."
-#    managed_by: "Managed by"
-#    will_be_cancelled: "Will be cancelled on"
-#    currently_free: "You currently have a free subscription"
-#    currently_free_until: "You currently have a subscription until"
-#    free_subscription: "Free subscription"
-#    was_free_until: "You had a free subscription until"
-#    managed_subs: "Managed Subscriptions"
-#    subscribing: "Subscribing..."
-#    current_recipients: "Current Recipients"
-#    unsubscribing: "Unsubscribing"
-#    subscribe_prepaid: "Click Subscribe to use prepaid code"
-#    using_prepaid: "Using prepaid code for monthly subscription"
-#    feature_level_access: "Access 300+ levels available"
-#    feature_heroes: "Unlock exclusive heroes and pets"
-#    feature_learn: "Learn to make games and websites"
-#    month_price: "$__price__"
-#    first_month_price: "Only $__price__ for your first month!"
-#    lifetime: "Lifetime Access"
-#    lifetime_price: "$__price__"
-#    year_subscription: "Annual Subscription"
-#    year_price: "$__price__/year"
-#    support_part1: "Need help with payment or prefer PayPal? Email"
-#    support_part2: "support@codecombat.com"
-
-#  announcement:
-#    now_available: "Now available for subscribers!"
-#    subscriber: "subscriber"
-#    cuddly_companions: "Cuddly Companions!" # Pet Announcement Modal
-#    kindling_name: "Kindling Elemental"
-#    kindling_description: "Kindling Elementals just want to keep you warm at night. And during the day. All the time, really."
-#    griffin_name: "Baby Griffin"
-#    griffin_description: "Griffins are half eagle, half lion, all adorable."
-#    raven_name: "Raven"
-#    raven_description: "Ravens are excellent at gathering shiny bottles full of health for you."
-#    mimic_name: "Mimic"
-#    mimic_description: "Mimics can pick up coins for you. Move them on top of coins to increase your gold supply."
-#    cougar_name: "Cougar"
-#    cougar_description: "Cougars like to earn a PhD by Purring Happily Daily."
-#    fox_name: "Blue Fox"
-#    fox_description: "Blue foxes are very clever and love digging in the dirt and snow!"
-#    pugicorn_name: "Pugicorn"
-#    pugicorn_description: "Pugicorns are some of the rarest creatures and can cast spells!"
-#    wolf_name: "Wolf Pup"
-#    wolf_description: "Wolf pups excel in hunting, gathering, and playing a mean game of hide-and-seek!"
-#    ball_name: "Red Squeaky Ball"
-#    ball_description: "ball.squeak()"
-#    collect_pets: "Collect pets for your heroes!"
-#    each_pet: "Each pet has a unique helper ability!"
-#    upgrade_to_premium: "Become a {{subscriber}} to equip pets."
-#    play_second_kithmaze: "Play {{the_second_kithmaze}} to unlock the Wolf Pup!"
-#    the_second_kithmaze: "The Second Kithmaze"
-#    keep_playing: "Keep playing to discover the first pet!"
-#    coming_soon: "Coming soon"
-#    ritic: "Ritic the Cold" # Ritic Announcement Modal
-#    ritic_description: "Ritic the Cold. Trapped in Kelvintaph Glacier for countless ages, finally free and ready to tend to the ogres that imprisoned him."
-#    ice_block: "A block of ice"
-#    ice_description: "There appears to be something trapped inside..."
-#    blink_name: "Blink"
-#    blink_description: "Ritic disappears and reappears in a blink of an eye, leaving nothing but a shadow."
-#    shadowStep_name: "Shadowstep"
-#    shadowStep_description: "A master assassin knows how to walk between the shadows."
-#    tornado_name: "Tornado"
-#    tornado_description: "It is good to have a reset button when one's cover is blown."
-#    wallOfDarkness_name: "Wall of Darkness"
-#    wallOfDarkness_description: "Hide behind a wall of shadows to prevent the gaze of prying eyes."
-
-#  avatar_selection:
-#    pick_an_avatar: "Pick an avatar that will represent you as a player"
-
-#  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 Griffin 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"
+    price: "x{{gems}} / mån"
+    buy_premium: "Köp Premium"
+    purchase: "Köp"
+    purchased: "Köpt"
+
+  subscribe_for_gems:
+    prompt_title: "Inte tillräckligt med ädelstenar!"
+    prompt_body: "Prenumerera på Premium för att få ädelstenar och få tillgång till ännu fler nivåer!"
+
+  earn_gems:
+    prompt_title: "Inte tillräckligt med ädelstenar"
+    prompt_body: "Fortsätt spela för att tjäna mer!"
+
+  subscribe:
+    best_deal: "Bästa erbjudandet!"
+    confirmation: "Grattis! Du har nu en CodeCombat Premium-prenumeration!"
+    premium_already_subscribed: "Du prenumererar redan på Premium!"
+    subscribe_modal_title: "CodeCombat Premium"
+    comparison_blurb: "Bli en Master Coder - prenumerera på Premium idag!"
+    must_be_logged: "Du måste vara inloggad först. Vänligen skapa ett konto eller logga in från menyn ovan."
+    subscribe_title: "Prenumerera" # Actually used in subscribe buttons, too
+    unsubscribe: "Avsluta prenumeration"
+    confirm_unsubscribe: "Bekräfta avslutande av prenumerationen"
+    never_mind: "Glöm det, jag älskar dig fortfarande"
+    thank_you_months_prefix: "Tack för att du stöttade oss dessa senaste"
+    thank_you_months_suffix: "månaderna."
+    thank_you: "Tack för att du stöder CodeCombat."
+    sorry_to_see_you_go: "Ledsen att se dig gå! Berätta gärna hur vi kan bli bättre."
+    unsubscribe_feedback_placeholder: "Oj, vad har vi gjort?"
+    stripe_description: "Månadsprenumeration"
+    stripe_yearly_description: "Årlig prenumeration"
+    buy_now: "Köp nu"
+    subscription_required_to_play: "Du behöver ett abonnemang för att spela den här nivån."
+    unlock_help_videos: "Prenumerera för att låsa upp alla självstudier."
+    personal_sub: "Personlig prenumeration" # Accounts Subscription View below
+    loading_info: "Laddar prenumerationsinformation ..."
+    managed_by: "Hanteras av"
+    will_be_cancelled: "Avslutas den"
+    currently_free: "Du har för närvarande en gratis prenumeration"
+    currently_free_until: "Du har för närvarande en prenumeration till"
+    free_subscription: "Gratis prenumeration"
+    was_free_until: "Du hade en gratis prenumeration fram till"
+    managed_subs: "Hanterade prenumerationer"
+    subscribing: "Prenumererar..."
+    current_recipients: "Nuvarande mottagare"
+    unsubscribing: "Avregistrerar dig"
+    subscribe_prepaid: "Klicka på Prenumerera för att använda förbetald kod"
+    using_prepaid: "Använd förbetald kod för månadsabonnemang"
+    feature_level_access: "Få tillgång till mer än 300 nivåer"
+    feature_heroes: "Lås upp exklusiva hjältar och husdjur"
+    feature_learn: "Lär dig att skapa spel och webbplatser"
+    month_price: "$__price__"
+    first_month_price: "Endast $__price__ för din första månad!"
+    lifetime: "Livstidsåtkomst"
+    lifetime_price: "$__price__"
+    year_subscription: "Årlig prenumeration"
+    year_price: "$__price__/år"
+    support_part1: "Behöver du hjälp med betalning eller föredrar PayPal? E-post"
+    support_part2: "support@codecombat.com"
+
+  announcement:
+    now_available: "Nu tillgängligt för prenumeranter!"
+    subscriber: "prenumeranter"
+    cuddly_companions: "Gosiga följeslagare!" # Pet Announcement Modal
+    kindling_name: "Kindling Elemental"
+    kindling_description: "Kindling Elementals vill bara hålla dig varm på natten. Och under dagen. Hela tiden, verkligen."
+    griffin_name: "Baby Griffin"
+    griffin_description: "Griffins är halv örn, halv lejon, alla bedårande."
+    raven_name: "Korpar"
+    raven_description: "Korpar är utmärkta för att samla blanka flaskor fulla av hälsa åt dig."
+    mimic_name: "Mimic"
+    mimic_description: "Mimics kan plocka upp mynt åt dig. Flytta dem ovanpå mynt för att öka din guldförsörjning."
+    cougar_name: "Pumor"
+    cougar_description: "Pumor gillar att ta doktorsexamen av Purring Happily Daily."
+    fox_name: "Blå räv"
+    fox_description: "Blå rävar är väldigt smarta och älskar att gräva i smuts och snö!"
+    pugicorn_name: "Pugicorn"
+    pugicorn_description: "Pugicorns är några av de sällsynta varelserna och kan använda magi!"
+    wolf_name: "Vargunge"
+    wolf_description: "Vargungar utmärker sig i att jaga, samla och spela ett genomsnittligt spel av kurra-gömma!"
+    ball_name: "Röd knarrande boll"
+    ball_description: "ball.squeak()"
+    collect_pets: "Samla husdjur till dina hjältar!"
+    each_pet: "Varje husdjur har en unik hjälparförmåga!"
+    upgrade_to_premium: "Bli en {{subscriber}} för att utrusta husdjur."
+    play_second_kithmaze: "Spela {{the_second_kithmaze}} för att låsa upp Vargunge!"
+    the_second_kithmaze: "Andra Kithmaze"
+    keep_playing: "Fortsätt spela för att upptäcka det första husdjuret!"
+    coming_soon: "Kommer snart"
+    ritic: "Ritic den kalle" # Ritic Announcement Modal
+    ritic_description: "Ritic den kalle. Fångad i Kelvintaph-glaciären i otaliga åldrar, äntligen fri och redo att ta sig till trollen som fängslade honom."
+    ice_block: "Ett block av is"
+    ice_description: "Det verkar vara något som är fångat inuti..."
+    blink_name: "Blinka"
+    blink_description: "Ritic försvinner och dyker upp igen på ett ögonblick och lämnar inget annat än en skugga."
+    shadowStep_name: "Skugg-gång"
+    shadowStep_description: "En mästare mördare vet hur man går mellan skuggorna."
+    tornado_name: "Tornado"
+    tornado_description: "Det är bra att ha en återställningsknapp när man blir avslöjad."
+    wallOfDarkness_name: "Vägg av mörker"
+    wallOfDarkness_description: "Dölj dig bakom en skuggvägg för att förhindra at bli sedd av nyfikna ögon."
+
+  avatar_selection:
+    pick_an_avatar: "Välj en avatar som representerar dig som spelare"
+
+  premium_features:
+    get_premium: "Skaffa
CodeCombat
Premium" # Fit into the banner on the /features page
+    master_coder: "Bli en Master Coder genom att prenumerera idag!"
+    paypal_redirect: "Du kommer att omdirigeras till PayPal för att slutföra prenumerationsprocessen."
+    subscribe_now: "Prenumerera nu"
+    hero_blurb_1: "Få tillgång till __premiumHeroesCount__ superladdade prenumeranter. Utnyttja den ostoppbara kraften hos Okar Stompfoot, den dödliga precisionen hos Naria of the Leaf, eller kalla till \"bedårande\" skelett med Nalfar Cryptor."
+    hero_blurb_2: "Premium Warriors låser upp fantastiska kampsport som Warcry, Stomp och Hurl Enemy. Eller spela som en Ranger, med smyg och bågar, kastknivar, fällor! Testa din skicklighet som en riktig kodningstrollkarl och släpp loss ett kraftfullt urval av primordial, nekromantisk eller elementär magi!"
+    hero_caption: "Spännande nya hjältar!"
+    pet_blurb_1: "Husdjur är inte bara bedårande följeslagare, de ger också unik funktionalitet och metoder. Baby Griffin kan bära enheter genom luften, Vargungar fångar fiendens pilar, Pumor är förtjust i att jaga ograr och Mimic plockar mynt som en magnet!"
+    pet_blurb_2: "Samla alla husdjur för att upptäcka deras unika förmågor!"
+    pet_caption: "Välj husdjur att följa med din hjälte!"
+    game_dev_blurb: "Lär dig skriptspel och bygg nya nivåer för att dela med dina vänner! Placera de föremål du vill ha, skriv kod för enhetslogik och beteende och se om dina vänner kan klara nivån!"
+    game_dev_caption: "Skapa dina egna spel för att utmana dina vänner!"
+    everything_in_premium: "Allt du får med CodeCombat Premium:"
+    list_gems: "Få bonusädelstenar för att köpa redskap, husdjur och hjältar"
+    list_levels: "Få tillgång till __premiumLevelsCount__ extra nivåer"
+    list_heroes: "Lås upp exklusiva hjältar, så som Ranger och Wizard"
+    list_game_dev: "Skapa och dela spel med vänner"
+    list_web_dev: "Bygg webbplatser och interaktiva appar"
+    list_items: "Utrusta dig med Premium föremål så som husdjur"
+    list_support: "Få Premium-support för att hjälpa dig felsöka knepig kod"
+    list_clans: "Skapa privata klaner för att bjuda in dina vänner och tävla på en leaderboard"

   choose_hero:
     choose_hero: "Välj hjälte"
@@ -1023,8 +1023,8 @@
     javascript_blurb: "Webbens språk. (Inte samma sak som Java.)"
     coffeescript_blurb: "Trevligare JavaScript-syntax."
     lua_blurb: "Språk för spelskript."
-#    java_blurb: "(Subscriber Only) Android and enterprise."
-#    cpp_blurb: "(Subscriber Only) Game development and high performance computing."
+    java_blurb: "(Endast för prenumerant) Android och företag."
+    cpp_blurb: "(Endast för prenumerant) Spelutveckling och högpresterande datorer."
     status: "Status"
     weapons: "Vapen"
     weapons_warrior: "Svärd - Kort räckvidd, ingen magi"
@@ -1050,12 +1050,12 @@
     level_to_unlock: "Nivå som låser upp:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
     restricted_to_certain_heroes: "Det är bara vissa hjältar som kan spela den här nivån."

-#  char_customization_modal:
-#    heading: "Customize Your Hero"
-#    body: "Body"
-#    name_label: "Hero's Name"
-#    hair_label: "Hair Color"
-#    skin_label: "Skin Color"
+  char_customization_modal:
+    heading: "Anpassa din hjälte"
+    body: "Kropp"
+    name_label: "Hjältens namn"
+    hair_label: "Hårfärg"
+    skin_label: "Hudfärg"

   skill_docs:
     function: "funktion" # skill types
@@ -1085,7 +1085,7 @@
     optional_parameters: "Valfria parametrar"
     returns: "Returnerar"
     granted_by: "Ges av"
-#    still_undocumented: "Still undocumented, sorry."
+    still_undocumented: "Fortfarande odokumenterad, sorry."

   save_load:
     granularity_saved_games: "Sparat"
@@ -1097,7 +1097,7 @@
     music_label: "Musik"
     music_description: "Stäng av/sätt på bakgrundsmusik."
     editor_config_title: "Redigerarinställningar"
-#    editor_config_livecompletion_label: "Live Autocompletion"
+    editor_config_livecompletion_label: "Live autoslutförande"
     editor_config_livecompletion_description: "Visar förslag medans du skriver"
     editor_config_invisibles_label: "Visa osynliga"
     editor_config_invisibles_description: "Visar osynliga tecken, till exempel mellanrum och nyradstecken."


Update uk.coffee (#5924)

Update uk.coffee (#5924)

* Update uk.coffee

* Update uk.coffee

Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>

--- app/locale/uk.coco.coffee
+++ app/locale/uk.coco.coffee
@@ -1,11 +1,11 @@
 module.exports = nativeDescription: "Українська", englishDescription: "Ukrainian", translation:

   new_home:
-#    title: "CodeCombat - Coding games to learn Python and JavaScript"
+    title: "CodeCombat - Кодування ігор для вивчення Python та JavaScript"
 #    meta_keywords: "CodeCombat, python, javascript, Coding Games"
 #    meta_description: "Learn typed code through a programming game. Learn Python, JavaScript, and HTML as you solve puzzles and learn to make your own coding games and websites."
 #    meta_og_url: "https://codecombat.com"
-#    become_investor: "to become an investor in CodeCombat"
+    become_investor: "стати інвестором у CodeCombat"
 #    built_for_teachers_title: "A Coding Game Built with Teachers in Mind"
 #    built_for_teachers_blurb: "Teaching kids to code can often feel overwhelming. CodeCombat helps all educators teach students how to code in either JavaScript or Python, two of the most popular programming languages. With a comprehensive curriculum that includes six computer science units and reinforces learning through project-based game development and web development units, kids will progress on a journey from basic syntax to recursion!"
 #    built_for_teachers_subtitle1: "Computer Science"
@@ -17,7 +17,7 @@
 #    century_skills_title: "21st Century Skills"
 #    century_skills_blurb1: "Students Don't Just Level Up Their Hero, They Level Up Themselves"
 #    century_skills_quote1: "You mess up…so then you think about all of the possible ways to fix it, and then try again. I wouldn't be able to get here without trying hard."
-#    century_skills_subtitle1: "Critical Thinking"
+    century_skills_subtitle1: "Критичне мислення"
 #    century_skills_subblurb1: "With coding puzzles that are naturally scaffolded into increasingly challenging levels, CodeCombat's programming game ensures kids are always practicing critical thinking."
 #    century_skills_quote2: "Everyone else was making mazes, so I thought, ‘capture the flag’ and that’s what I did."
 #    century_skills_subtitle2: "Creativity"
@@ -34,7 +34,7 @@
 #    classroom_in_box_blurb3: "Inspire all school leaders to create a world-class computer science program."
 #    classroom_in_box_blurb4: ""
 #    covid19_banner_blurb: "We are offering free access to our full curriculum to educators and schools affected by school closures."
-#    click_here: "Click here"
+    click_here: "Тиснить тут"
 #    creativity_rigor_title: "Where Creativity Meets Rigor"
 #    creativity_rigor_subtitle1: "Make coding fun and teach real-world skills"
 #    creativity_rigor_blurb1: "Students type real Python and JavaScript while playing games that encourage trial-and-error, critical thinking, and creativity. Students then apply the coding skills they’ve learned by developing their own games and websites in project-based courses."
@@ -43,7 +43,7 @@
 #    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
 #    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
 #    featured_partners_title1: "Featured In"
-#    featured_partners_title2: "Awards & Partners"
+    featured_partners_title2: "Нагороди та партнери"
 #    featured_partners_blurb1: "CollegeBoard Endorsed Provider"
 #    featured_partners_blurb2: "Best Creativity Tool for Students"
 #    featured_partners_blurb3: "Top Pick for Learning"
@@ -76,14 +76,14 @@
 #    global_subtitle1: "Learners"
 #    global_subtitle2: "Lines of Code"
     global_subtitle3: "Вчителі"
-#    global_subtitle4: "Countries"
-#    go_to_my_classes: "Go to my classes"
-#    go_to_my_courses: "Go to my courses"
+    global_subtitle4: "Країни"
+    go_to_my_classes: "Йти до мого класу"
+    go_to_my_courses: "Йти до моїх курсів"
 #    quotes_quote1: "Name any program online, I’ve tried it. None of them match up to CodeCombat. Any teacher who wants their students to learn how to code... start here!"
 #    quotes_quote2: " I was surprised about how easy and intuitive CodeCombat makes learning computer science. The scores on the AP exam were much higher than I expected and I believe CodeCombat is the reason why this was the case."
 #    quotes_quote3: "CodeCombat has been the most beneficial for teaching my students real-life coding capabilities. My husband is a software engineer and he has tested out all of my programs. He put this as his top choice."
 #    quotes_quote4: "The feedback … has been so positive that we are structuring a computer science class around CodeCombat. The program really engages the students with a gaming style platform that is entertaining and instructional at the same time. Keep up the good work, CodeCombat!"
-#    see_example: "See example"
+    see_example: "Дивитись приклад"
     slogan: "Найзахопливіша гра для вивчення програмування." # {change}
 #    teach_cs1_free: "Teach CS1 Free"
 #    teachers_love_codecombat_title: "Teachers Love CodeCombat"
@@ -92,7 +92,7 @@
 #    teachers_love_codecombat_blurb3: "Say that CodeCombat helps them support students’ problem solving abilities"
 #    teachers_love_codecombat_subblurb: "In partnership with McREL International, a leader in research-based guidance and evaluations of educational technology."
 #    top_banner_blurb: "Parents, book your child's first live online coding class for free!"
-#    try_the_game: "Try the game"
+    try_the_game: "Спробуй гру"
     classroom_edition: "Класна версія:"
     learn_to_code: "Вчитися кодувати:"
     play_now: "Грати зараз"
@@ -163,12 +163,12 @@
 #    ozaria: "Ozaria"
 #    codecombat_blurb: "Our original coding game. Recommended for parents, individuals, educators, and students who want to experience one of the most-loved coding games in the world."
 #    ozaria_blurb: "An adventure game and Computer Science program where students master the lost magic of coding to save their world. Recommended for educators and students."
-#    try_codecombat: "Try CodeCombat"
-#    try_ozaria: "Try Ozaria"
+    try_codecombat: "Спробуй CodeCombat"
+    try_ozaria: "Спробуй Ozaria"

   nav:
 #    educators: "Educators"
-#    follow_us: "Follow Us"
+    follow_us: "Слідкуй за нами"
 #    general: "General"
     map: "Карта"
     play: "Грати" # The top nav bar entry where players choose which levels to play
@@ -228,7 +228,7 @@
     subscribe_as_diplomat: "Записатися в Дипломати"

   play:
-#    title: "Play CodeCombat Levels - Learn Python, JavaScript, and HTML"
+    title: "Грай CodeCombat-ові рівні - вивчай Python, JavaScript та HTML"
 #    meta_description: "Learn programming with a coding game for beginners. Learn Python or JavaScript as you solve mazes, make your own games, and level up. Challenge your friends in multiplayer arena levels!"
 #    level_title: "__level__ - Learn to Code in Python, JavaScript, HTML"
 #    video_title: "__video__ | Video Level"
@@ -236,11 +236,14 @@
 #    web_development_title: "__level__ | Web Development"
     anon_signup_title_1: "CodeCombat має"
     anon_signup_title_2: "Класний Версію!"
+    anon_signup_enter_code: "Введить код класу:"
+    anon_signup_ask_teacher: "Ще немає? Запитай у свого вчителя!"
+    anon_signup_create_class: "Бажаєте створити клас?"
 #    anon_signup_enter_code: "Enter Class Code:"
 #    anon_signup_ask_teacher: "Don't have one? Ask your teacher!"
 #    anon_signup_create_class: "Want to create a class?"
 #    anon_signup_setup_class: "Set up a class, add your students, and monitor progress!"
-#    anon_signup_create_teacher: "Create free teacher account"
+    anon_signup_create_teacher: "Створити безкоштовний акаунт вчителя"
     play_as: "Грати за" # Ladder page
     get_course_for_class: "Призначте розробку ігор та інше для ваших класів!"
     request_licenses: "Зверніться до наших шкільних фахівців для подробиць."
@@ -430,10 +433,10 @@
     teacher_additional_questions: "Отакої! Якщо вам потрібна додаткова допомога або у вас є питання, зв'яжіться з __supportEmail__."
     dont_use_our_email_silly: "Не пишіть нашу електронний адресу здесь! Введіть електронну адресу вашого батька."
     want_codecombat_in_school: "Хочете грати в CodeCombat весь час?"
-#    eu_confirmation: "I agree to allow CodeCombat to store my data on US servers."
-#    eu_confirmation_place_of_processing: "Learn more about the possible risks"
-#    eu_confirmation_student: "If you are not sure, ask your teacher."
-#    eu_confirmation_individual: "If you do not want us to store your data on US servers, you can always keep playing anonymously without saving your code."
+    eu_confirmation: "Я згоден дозволити CodeCombat зберігати мої дані на серверах США."
+    eu_confirmation_place_of_processing: "Дізнайтеся більше про можливі ризики"
+    eu_confirmation_student: "Якщо ви не впевнені, дізнайтеся у вашого вчителя."
+    eu_confirmation_individual: "Якщо ви не бажаєте сберігати свої дані на серверах США, то ви завжди можете грати анонімно без збереження свого коду."

   recover:
     recover_account_title: "Відновити обліківку"
@@ -449,7 +452,7 @@
     books: "Книги"

   common:
-#    default_title: "CodeCombat - Coding games to learn Python and JavaScript"
+    default_title: "CodeCombat - Кодування ігор для вивчення Python та JavaScript"
 #    default_meta_description: "Learn typed code through a programming game. Learn Python, JavaScript, and HTML as you solve puzzles and learn to make your own coding games and websites."
     back: "Назад" # When used as an action verb, like "Navigate backward"
     coming_soon: "Очікуйте скоро!"
@@ -482,9 +485,9 @@
     submit_changes: "Надіслати зміни"
     save_changes: "Зберегти зміни"
     required_field: "обов'язкове поле"
-#    submit: "Submit"
-#    replay: "Replay"
-#    complete: "Complete"
+    submit: "Надіслати"
+    replay: "Переграти"
+    complete: "Завершити"

   general:
     and: "та"
@@ -586,7 +589,7 @@
 #    concept_challenge_complete: "Concept Challenge Complete!"
 #    combo_challenge_complete: "Combo Challenge Complete!"
 #    combo_challenge_complete_body: "Great job, it looks like you're well on your way to understanding __concept__!"
-#    replay_level: "Replay Level"
+    replay_level: "Переграти рівень"
 #    combo_concepts_used: "__complete__/__total__ Concepts Used"
 #    combo_all_concepts_used: "You used all concepts possible to solve the challenge. Great job!"
 #    combo_not_all_concepts_used: "You used __complete__ out of the __total__ concepts possible to solve the challenge. Try to get all __total__ concepts next time!"
@@ -643,7 +646,7 @@
     tome_cast_button_run: "Виконати"
     tome_cast_button_running: "Виконання"
     tome_cast_button_ran: "Виконати"
-#    tome_cast_button_update: "Update"
+    tome_cast_button_update: "Відновити"
     tome_submit_button: "Підтвердити"
     tome_reload_method: "Відновити оригінальний код щоб перезапустити рівень"
     tome_available_spells: "Доступні заклинання"
@@ -655,8 +658,8 @@
     skip_tutorial: "Пропустити (esc)"
     keyboard_shortcuts: "Клавіатурні скорочення"
     loading_start: "Розпочати рівень"
-#    loading_start_combo: "Start Combo Challenge"
-#    loading_start_concept: "Start Concept Challenge"
+    loading_start_combo: "Розпочати Combo Випробування"
+    loading_start_concept: "Розпочати Concept Випробування"
     problem_alert_title: "Виправте код"
     time_current: "Зараз:"
     time_total: "Найбільше:"
@@ -748,8 +751,8 @@
 #    concept_unlocked: "Concept Unlocked"
 #    use_at_least_one_concept: "Use at least one concept: "
 #    command_bank: "Command Bank"
-#    learning_goals: "Learning Goals"
-#    start: "Start"
+    learning_goals: "Цілі навчання"
+    start: "Розпочати"
 #    vega_character: "Vega Character"
     click_to_continue: "Клацніть, щоб продовжити"

@@ -757,8 +760,8 @@
     methods: "Методи"
     events: "Події"
 #    handlers: "Handlers"
-#    properties: "Properties"
-#    snippets: "Snippets"
+    properties: "Властивості"
+    snippets: "Сніппети"
     spawnable: "Породжуваний"
     html: "HTML"
     math: "Математика"
@@ -773,7 +776,7 @@
     number: "Число"
     webjavascript: "JavaScript"

-#  amazon_hoc:
+  amazon_hoc:
 #    title: "Keep Learning with 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."
@@ -784,10 +787,10 @@
 #    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."

-#  live_class:
-#    title: "Thank you!"
-#    content: "Amazing! We just launched live online classes."
-#    link: "Ready to get ahead on your coding?"
+  live_class:
+#    title: "Дякую!"
+    content: "Вражаюче! Ми зараз запустили онлайн-клас."
+    link: "Вже готові прямувати до кодування?"

 #  code_quest:
 #    great: "Great!"
@@ -979,7 +982,7 @@
   premium_features:
     get_premium: "Отримайте
CodeCombat
Преміум" # Fit into the banner on the /features page
     master_coder: "Станьте майстром програмування придбавши підписку вже сьогодні!"
-#    paypal_redirect: "You will be redirected to PayPal to complete the subscription process."
+    paypal_redirect: "Вас спрямують до PayPal для завершення підписки."
     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!"
@@ -1037,7 +1040,7 @@
     restricted_to_certain_heroes: "Тільки певні герої можуть грати в цьому рівні."

   char_customization_modal:
-#    heading: "Customize Your Hero"
+    heading: "Налаштуйте свого героя"
     body: "Тіло"
     name_label: "Ім'я героя"
     hair_label: "Колір волосся"
@@ -1286,7 +1289,7 @@
     create_account_subtitle: "Отримайте доступ до інструментів, доступних лише вчителям, для використання CodeCombat в класі. Налаштуйте клас, додайте учнів та слідкуйте за їхнім прогресом!"
     convert_account_title: "Оновити до обліківки вчителя"
     not: "Ні"
-#    full_name_required: "First and last name required"
+    full_name_required: "Необхідні ім'я та призвище"

   versions:
     save_version_title: "Зберегти нову версію"
@@ -1502,10 +1505,10 @@
     teacher: "Вчитель"
     arena: "Арена"
     available_levels: "Доступні рівні"
-#    started: "started"
-#    complete: "complete"
-#    practice: "practice"
-#    required: "required"
+    started: "розпочато"
+    complete: "закінчено"
+    practice: "практикування"
+    required: "необхідно"
     welcome_to_courses: "Шукачі пригод, ласкаво просимо до курсів!"
     ready_to_play: "Готові грати?"
     start_new_game: "Розпочати нову гру"
@@ -1517,9 +1520,9 @@
     welcome_to_page: "Моя учнівська панель"
     my_classes: "Поточні класи"
     class_added: "Клас успішно додано!"
-#    view_map: "view map"
-#    view_videos: "view videos"
-#    view_project_gallery: "view my classmates' projects"
+    view_map: "дивитись карту"
+    view_videos: "дивитись відео"
+    view_project_gallery: "дивитись проекти однокласників"
     join_class: "Приєднайтесь до класу"
     join_class_2: "Приєднайтесь до класу"
     ask_teacher_for_code: "Запитайте у вашого вчителя, чи є у вас код класу CodeCombat! Якщо так, введіть його нижче:"
@@ -1538,7 +1541,7 @@
     are_you_sure: "Ви впевнені, що хочете видалити цього учня з цього класу?"
     remove_description1: "Учень втратить доступ до цієї класної кімнати та призначених класів. Прогрес НЕ буде втрачено і учня можна буде додати знову до класу в будь-який момент."
     remove_description2: "Активовану платну ліцензію не буде повернено."
-#    license_will_revoke: "This student's paid license will be revoked and made available to assign to another student."
+    license_will_revoke: "Ця студентська сплачена ліцензія буде відкликана та буде доступною для призначення іншому студенту."
     keep_student: "Зберегти учня"
     removing_user: "Видалення користувача"
     subtitle: "Переглянути опис та рівні курсу" # Flat style redesign
@@ -1566,8 +1569,8 @@
     student_age_range_to: "до"
 #    estimated_class_dates_label: "Estimated Class Dates"
 #    estimated_class_frequency_label: "Estimated Class Frequency"
-#    classes_per_week: "classes per week"
-#    minutes_per_class: "minutes per class"
+    classes_per_week: "класів на тиждень"
+    minutes_per_class: "хвилин на клас"
     create_class: "Створити клас"
     class_name: "Назва класу"
     teacher_account_restricted: "Ваша обліківка - вчительська і не дає доступу до учнівських матеріалів."
@@ -1595,7 +1598,7 @@
     update_account_students_can3: "Змагайтеся з однокласниками на аренах"
     update_account_students_can4: "Отримувати доступ до нового учнівського функціоналу як тільки ми випускаємо його"
     update_account_students_warning: "УВАГА: Ви не зможете створювати нових та керувати жодним з існуючих класів, які ви раніше створили."
-#    unsubscribe_warning: "Warning: You will be unsubscribed from your monthly subscription."
+    unsubscribe_warning: "Увага: ви будете відписані від щомісячної підписки."
     update_account_remain_student: "Залишитися учнем"
     update_account_update_student: "Оновити до обліківки учня"
     need_a_class_code: "Вам буде потрібен Код Класу для класу, до якого ви долучаєтесь:"
@@ -1615,7 +1618,7 @@
     select_your_hero_description: "Ви завжди можете змінити вашого героя на сторінці ваших курсів клікнувши \"Змінити героя\""
     select_this_hero: "Обрати цього героя"
     current_hero: "Поточний герой:"
-#    current_hero_female: "Current Hero:"
+    current_hero_female: "Поточна геройка:"
     web_dev_language_transition: "Програма HTML/JavaScript для всіх класів для цього курсу. Класи, що використовували Python, розпочнуть з додаткових вступних рівнів JavaScript, щоб полегшити перехід. Для класів, що вже використовують JavaScript, вступні рівні буде пропущено."
     course_membership_required_to_play: "Вам потрібно приєднатись до курсу щоб зіграти в цей рівень."
     license_required_to_play: "Попроси свого вчителя призначити тобі ліцензію для продовження гри на CodeCombat!"


Change simulated operations generation (several month + add some latency)

--- server/lib/weboob-mock.coffee
+++ server/lib/weboob-mock.coffee
@@ -85,11 +85,16 @@
 randomLabelPositive = () ->
     return randomLabelsPositive[rand 0, randomLabelsPositive.length]

-generateDate = (low, high) ->
-    moment().date(rand(low, high)).format('YYYY-MM-DDT00:00:00.000[Z]')
+generateDate = (lowDay, highDay, lowMonth, highMonth) ->
+    moment()
+        .month(rand(lowMonth, highMonth))
+        .date(rand(lowDay, highDay))
+        .format('YYYY-MM-DDT00:00:00.000[Z]')

 generateOne = (account) ->
     n = rand 0, 100
+
+    now = moment()

     if n < 2
         # on 4th of Month
@@ -97,14 +102,14 @@
             "label": "Loyer"
             "raw": "Loyer habitation"
             "amount": "-300"
-            "rdate": generateDate 4, 4
+            "rdate": generateDate 4, 4, now.month(), now.month()
             "account": account
         }

     if n < 15
         [label, raw] = randomLabelPositive()
         amount = rand(100, 800) + rand(0, 100) / 100
-        rdate = generateDate 0, moment().date()
+        rdate = generateDate 1, moment().date(), 0, now.month()
         return {
             label: label
             raw: raw
@@ -115,7 +120,7 @@

     [label, raw] = randomLabel()
     amount = -rand(0, 60) + rand(0, 100) / 100
-    rdate = generateDate 0, moment().date()
+    rdate = generateDate 1, moment().date(), 0, now.month()
     return {
         label: label
         raw: raw
@@ -147,6 +152,8 @@
     operations

 exports.FetchOperations = (bankuuid, login, password, website, callback) ->
-    output = {}
-    output[bankuuid] = generate bankuuid
-    callback null, output
+    setTimeout () ->
+        output = {}
+        output[bankuuid] = generate bankuuid
+        callback null, output
+    , 1000


Whitelist microdata attributes

Whitelist microdata attributes

--- server/utils/jwz_tools.coffee
+++ server/utils/jwz_tools.coffee
@@ -48,12 +48,13 @@
             'head'
             'meta'
         ]
+        safeAttributes = ['style', 'class', 'background', 'itemscope', 'itemtype', 'itemprop', 'content']
         allowedAttributes = sanitizeHtml.defaults.allowedAttributes
         allowedTags.forEach (tag) ->
             if allowedAttributes[tag]?
-                allowedAttributes[tag] = allowedAttributes[tag].concat ['style', 'class', 'background']
+                allowedAttributes[tag] = allowedAttributes[tag].concat safeAttributes
             else
-                allowedAttributes[tag] = ['style', 'class', 'background']
+                allowedAttributes[tag] = safeAttributes
         html = sanitizeHtml html,
             allowedTags: allowedTags
             allowedAttributes: allowedAttributes


正しくコメントアウトできるよう修正

正しくコメントアウトできるよう修正

--- settings/language-review.cson
+++ settings/language-review.cson
@@ -1,3 +1,3 @@
-'.re':
+'.source.review':
   'editor':
-    'commentStart': '+@+'
+    'commentStart': '#@#'


:bug:Fix homepage video

:bug:Fix homepage video

vimeo Player is now an object with property default

--- app/views/HomeView.coffee
+++ app/views/HomeView.coffee
@@ -114,7 +114,7 @@

   afterRender: ->
     require.ensure(['@vimeo/player'], (require) =>
-      Player = require('@vimeo/player')
+      Player = require('@vimeo/player').default
       @vimeoPlayer = new Player(@$('.vimeo-player')[0])
     , (e) =>
       console.log e


[client][app][activity] changed query

[client][app][activity] changed query

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -187,7 +187,7 @@
     currentGroup     = groupsController.getCurrentGroup()

     fetch = (slug)=>
-      if slug isnt 'koding' or not KD.config.useNeo4j
+      if slug isnt 'koding' and not KD.config.useNeo4j
         @fetchActivitiesDirectly options, callback
       else
         @isExempt (exempt)=>


Fixed appManager.openApplication.

Fixed appManager.openApplication.

--- client/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -501,7 +501,7 @@
               @getSingleton('mainController').emit 'CreateNewActivityRequested', 'JCodeShare', CodeShares

   openTerminalFromHere: (nodeView) ->
-    appManager.openApplication "WebTerm", yes, (appInstance) =>
+    appManager.open "WebTerm", yes, (appInstance) =>
       path        = nodeView.getData().path
       webTermView = KD.getSingleton('mainView').mainTabView.getActivePane().mainView
       #TODO: webTermView != appInstance.getView() so should simplify the line above


optimized method for add childs to a molecule/organism

--- source/core/attributes.coffee
+++ source/core/attributes.coffee
@@ -31,7 +31,7 @@
       delete @attributes.entity

   chemistry: (children) ->
-    children = @attributes.children or @default.children
+    children = @attributes.children or @default.children or []
     for item, index in children
       for key of item when not @constructor.available or key in @constructor.available
         base = key.split(".")
@@ -41,15 +41,15 @@
           attributes = item[key]
           if @default?.children?[index]?[key]?
             attributes = Atoms.Core.Helper.mix item[key], @default.children?[index]?[key]
-          @children.push @instance type, class_name, attributes
+          @appendChild type, class_name, attributes

-  instance: (type, class_name, attributes={}) ->
+  appendChild: (type, class_name, attributes={}) ->
     if @__available type, class_name
       attributes.parent = attributes.parent or @
-      instance = new Atoms[type][class_name] attributes
-      @[attributes.id] = instance if attributes.id
-      instance
-
+      child = new Atoms[type][class_name] attributes
+      @children.push child
+      @[attributes.id] = child if attributes.id
+      child
     else
       throw "Instance no available in current container."


updated tests

updated tests

--- tests/test.coffee
+++ tests/test.coffee
@@ -2,6 +2,6 @@
 chai.should();

 describe "Addition", ->
-  it "equals 4", ->
-    result = 2 + 2
-    result.should.equal(4)
+  it "equals 6", ->
+    result = 3 + 3
+    result.should.equal(6)


avoid keyboard-jumping to ‘pure hangouts’, fixes #36

--- src/ui/models/viewstate.coffee
+++ src/ui/models/viewstate.coffee
@@ -43,7 +43,7 @@
     selectNextConv: (offset = 1) ->
         conv = require './conv'
         id = @selectedConv
-        list = conv.list()
+        list = (i for i in conv.list() when not conv.isPureHangout(c))
         for c, index in list
             if id == c.conversation_id.id
                 candidate = index + offset


html stylizer isn't ready

html stylizer isn't ready

--- src/vows/stylizers/html.coffee
+++ src/vows/stylizers/html.coffee
@@ -1,34 +0,0 @@
-exports ?= this
-
-exports.stylize = (str) -> new HTMLStylizer(str)
-
-escapeXML = (str) ->
-    str.replace(/&(?!\w{2,5};)/g, "&")
-       .replace(/\s/g, " ")
-       .replace(/\"/g, """)
-       .replace(/</g, "<")
-       .replace(/>/g, ">")
-       .replace(/\n/g, '
')
-
-class exports.HTMLStylizer extends exports.Stylizer
-    tags: {
-        bold      : ['b', null],
-        italic    : ['i', null],
-        underline : ['u', null],
-        label     : ['span', 'class="label"'],
-        key       : ['b', null]
-        special   : 'grey',      # null, undefined...
-        string:  'green',
-        number:  'magenta',
-        bool:    'blue',      # true false
-        regexp:  'green',     # /\d+/
-    }
-
-    constructor: (ob) ->
-        super(ob)
-        @str = escapeXML(@str)
-
-    stylize: (style) ->
-        switch style
-            when 'bold' then return @str = "#{str}"
-            when 'italic' then null


fix for v0

fix for v0

--- apis/v0.coffee
+++ apis/v0.coffee
@@ -128,8 +128,8 @@

     return res.send({success: false, errorMessage: 'Bad Login'}) if err

-    data.classes.unshift updateBuggers['classes']
-    data.classes.push updateBuggers['classes']
+    #data.classes.unshift updateBuggers['classes']
+    #data.classes.push updateBuggers['classes']
     res.send {
       success: true
       token: data.stud.session.token


Favicon was in the wrong place. I'm obviously not testing things before committing.

--- app/app.coffee
+++ app/app.coffee
@@ -23,7 +23,7 @@
 app.set 'views', path.join(__dirname, 'views', 'server')
 app.set 'view engine', 'jade'
 app.set 'assets', Util.getAssets(environment)
-app.use favicon(__dirname + '/public/favicon.ico')
+app.use favicon(path.join(__dirname, '../public/favicon.ico'))
 app.use logger 'dev'
 app.use express.static(path.join(__dirname, '..', 'public'))
 routes.setup app


disable experiment for now

disable experiment for now

--- app/lib/experiments.coffee
+++ app/lib/experiments.coffee
@@ -8,7 +8,7 @@
 If no experiments should be running right now, set this to null, false or ""
 WARNING: using a non-existent experiment will crash the webapp.
 ###
-activeExperiment = "SerengetiInterestingAnimalsExperiment1"
+activeExperiment = null

 currentExperiment = activeExperiment


Add console output to make it easier to spot mistakes caused by missing atlasses

--- app/models/TILEDMap.coffee
+++ app/models/TILEDMap.coffee
@@ -79,6 +79,8 @@
     @set 'tileSize':tileSize
     @set 'pixelSize':pixelSize

+    console.log 'Start loading atlasses'
+
     tilesets = for tileset in currMapData.tilesets
       @createTileSet tileset

@@ -108,6 +110,8 @@
       @imgLoadCount++
       if @imgLoadCount == currMapData.tilesets.length
         @set 'fullyLoaded': true
+      else
+        console.log "#{currMapData.tilesets.length - @imgLoadCount} to go"
     img.src = 'atlases/' + tileset.image.replace /^.*[\\\/]/, ''

     ts =
@@ -168,6 +172,8 @@
     canvas.width = numXTiles * tileSize.x
     canvas.height = numYTiles * tileSize.y

+    console.log 'Finish loading atlasses'
+
     for layer in currMapData.layers
       continue if layer.type isnt 'tilelayer'
       continue if layer.name is 'sound'


Add align to 74eo icon

Add align to 74eo icon

--- assets/data/constant.cson
+++ assets/data/constant.cson
@@ -110,13 +110,16 @@
     link: 'https://github.com/andanteyk/ElectronicObserver'
     avatar: 'https://github.com/andanteyk/ElectronicObserver/blob/develop/ElectronicObserver/Assets/AppIcon_64.png?raw=true'
     description: 'For providing KanColle API references.'
+    extraCSS:
+      margin: "auto"
+      display: "block"
   ,
     name: 'Kensuke Tanaka'
     link: 'https://www.facebook.com/kensuke.tanaka.790'
     avatar: 'http://www.famitsu.com/images/000/039/658/l_522b1c633860a.jpg'
     description: 'For providing development difficulties (which we enjoyed a lot, sincerely).'
     extraCSS:
-        maxHeight: "200%"
-        marginLeft: "-65%"
-        marginTop: "-20%"
+      maxHeight: "200%"
+      marginLeft: "-65%"
+      marginTop: "-20%"
 ]


Establish a Convention for Using Reflection to Automate Instantiation

--- src/coffee/experimental-controller.coffee
+++ src/coffee/experimental-controller.coffee
@@ -3,12 +3,12 @@
 class Quality
   constructor: (@name, @defaultValue) ->

-  initInstance: () ->
-    @value = @defaultValue
-
 instantiate = (proto) ->
   instance = Object.create proto
-  instance.initInstance()
+  for key, value of proto when /^default\w{2}/.test(key)
+    [_, first, rest] = /^default(\w)(\w*)/.exec(key)
+    newName = first.toLowerCase() + rest
+    instance[newName] = value
   return instance

 class Storylet


Changes for new job and job-collection dev versions

--- sample.coffee
+++ sample.coffee
@@ -20,10 +20,10 @@
    transform: (d) ->
       console.log "Transforming...", d
       try
-         res = myJobs.makeJob d
-         console.warn "makeJob succeeded in transform"
+         res = myJobs.createJob d
+         console.warn "succeeded in transform"
       catch e
-         console.warn "makeJob failed in transform"
+         console.warn "failed in transform"
          res = d
       return res


do not compare strings with integer

do not compare strings with integer

in js: ("13" == 13) is true, coffeescript is more strict by default

--- script.coffee
+++ script.coffee
@@ -1,4 +1,5 @@
 angular = window.angular
+console = window.console

 app = angular.module('gw2App', ['ngResource'])

@@ -11,7 +12,7 @@
   delete $http.defaults.headers.common['X-Requested-With']

   $scope.$on "$routeChangeSuccess", ($currentRoute, $previousRoute) ->
-    $scope.worldId = $routeParams.worldId
+    $scope.worldId = parseInt($routeParams.worldId)

   Worlds = $resource('https://api.guildwars2.com/v1/world_names.json')
   Matches = $resource('https://api.guildwars2.com/v1/wvw/matches.json')
@@ -21,7 +22,10 @@
   timer = null
   $scope.interval = 0

-  $scope.worlds = Worlds.query()
+  $scope.worlds = Worlds.query (records)->
+    for r in records
+      r.id = parseInt(r.id)
+
   $scope.redWorld = {}
   $scope.blueWorld = {}
   $scope.greenWorld = {}


Commented sound

Commented sound

--- app/views/tile_view.coffee
+++ app/views/tile_view.coffee
@@ -26,7 +26,7 @@
     'touchend'   : 'dragTileEnd'
   }

-  sound: new Audio('sounds/tink.mp3')
+  # sound: new Audio('sounds/tink.mp3')

   initialize: ->
     _.bindAll @, 'render', 'playTile', 'dragTileStart', 'dragTileMove', 'dragTileEnd'
@@ -49,7 +49,7 @@
   play: ->
     if @model.canBePlayed()
       @model.play()
-      @sound.play()
+      # @sound.play()
       game.set { moves: game.get('moves') + 1 }

   dragTileStart: (e) ->


Hopefully fixed drawing duplicate cards.

--- server.coffee
+++ server.coffee
@@ -345,8 +345,7 @@
 	cardId = deck[r]
 	card = questionCards[cardId]

-	i = deck.indexOf r
-	deck.splice(i, 1)
+	deck.splice(r, 1)
 	Db.shared.set 'questiondeck', deck
 	return card

@@ -359,8 +358,7 @@
 	cardId = deck[r]
 	card = answerCards[cardId]

-	i = deck.indexOf r
-	deck.splice(i, 1)
+	deck.splice(r, 1)
 	Db.shared.set 'answerdeck', deck
 	return card


configurable connect timeouts to remote endpoints

--- server.coffee
+++ server.coffee
@@ -11,6 +11,7 @@
 max_redirects   = process.env.CAMO_MAX_REDIRECTS   || 4
 camo_hostname   = process.env.CAMO_HOSTNAME        || "unknown"
 logging_enabled = process.env.CAMO_LOGGING_ENABLED || "disabled"
+connect_timeout = process.env.CAMO_CONNECT_TIMEOUT || 10

 log = (msg) ->
   unless logging_enabled == "disabled"
@@ -54,6 +55,10 @@
     log transferred_headers

     srcReq = src.request 'GET', query_path, transferred_headers
+
+    srcReq.setTimeout (connect_timeout * 1000), ()->
+      srcReq.end()
+      four_oh_four resp, "Timeout connecting to #{url.host}"

     srcReq.on 'response', (srcResp) ->
       is_finished = true


Typo.

Typo.

--- server.coffee
+++ server.coffee
@@ -494,7 +494,7 @@
         # To catch any errors.
         multi: true

-      throw new Error "Incosistent migration record state, missing migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}'" unless count is 1
+      throw new Error "Inconsistent migration record state, missing migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}'" unless count is 1
     else
       count = globals.Document.Migrations.find(
         migrationName: migration.name
@@ -504,7 +504,7 @@
         newVersion: newSchema
       ).count()

-      throw new Error "Incosistent migration record state, unexpected migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}'" unless count is 0
+      throw new Error "Inconsistent migration record state, unexpected migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}'" unless count is 0

       globals.Document.Migrations.insert
         # Things should not be running in parallel here anyway, so we can get next serial in this way.


aligned some sections of the code

aligned some sections of the code

--- server.coffee
+++ server.coffee
@@ -1,14 +1,14 @@
 #!/usr/bin/env coffee
-fs      = require 'fs'
-coffee  = require 'coffee-script'
-express = require 'express'
+fs          = require 'fs'
+coffee      = require 'coffee-script'
+express     = require 'express'
 compression = require 'compression'
-io      = require 'socket.io'
-net     = require 'net'
-http    = require 'http'
-https   = require 'https'
-uglify  = require 'uglify-js'
-cleanCSS = new (require 'clean-css')
+io          = require 'socket.io'
+net         = require 'net'
+http        = require 'http'
+https       = require 'https'
+uglify      = require 'uglify-js'
+cleanCSS    = new (require 'clean-css')

 opts = require('nomnom').options(
   addr: metavar: 'host:port', position: 0, default: '127.0.0.1:4502', help: 'address of the interpreter to connect to; default: 127.0.0.1:4502'
@@ -106,10 +106,10 @@
 app.use (req, res, next) -> log req.method + ' ' + req.path; next()
 app.use compression()
 app.disable 'x-powered-by'
-app.get '/',           (req, res) -> res.header('Content-Type', 'text/html'               ).send html
-app.get '/D.js',       (req, res) -> res.header('Content-Type', 'application/x-javascript').send js
-app.get '/apl385.ttf', (req, res) -> res.header('Content-Type', 'application/octet-stream').send ttf
-app.get '/favicon.ico',    (req, res) -> res.header('Content-Type', 'image/x-icon'            ).send ico
+app.get '/',            (req, res) -> res.header('Content-Type', 'text/html'               ).send html
+app.get '/D.js',        (req, res) -> res.header('Content-Type', 'application/x-javascript').send js
+app.get '/apl385.ttf',  (req, res) -> res.header('Content-Type', 'application/octet-stream').send ttf
+app.get '/favicon.ico', (req, res) -> res.header('Content-Type', 'image/x-icon'            ).send ico
 app.use '/help',     express.static __dirname + '/docs/help'
 app.use '/help.css', express.static __dirname + '/docs/help.css'
 app.use '/help.js',  express.static __dirname + '/docs/help.js'
@@ -119,7 +119,7 @@
     -> log "http server listening on :#{httpPort}"
 else
   httpsOptions =
-    key: fs.readFileSync 'ssl/key.pem'
+    key:  fs.readFileSync 'ssl/key.pem'
     cert: fs.readFileSync 'ssl/cert.pem'
   server = https.createServer(httpsOptions, app).listen (httpsPort = 8443),
     -> log "https server listening on :#{httpsPort}"


uncommented log output

uncommented log output

--- server.coffee
+++ server.coffee
@@ -56,12 +56,12 @@
     return rows
   )

+# uncomment if you want to see sum test output
+#getLastData(connection, 'pc1')
+#getLastData(connection, 'pc2')

-getLastData(connection, 'pc1')
-getLastData(connection, 'pc2')
-
-getAllData(connection, 'pc1')
-getAllData(connection, 'pc2')
+#getAllData(connection, 'pc1')
+#getAllData(connection, 'pc2')

 connection.end()


Prevent autolinker to process insice multiline block codes and only link urls containing :// or www.

--- packages/rocketchat-autolinker/autolinker.coffee
+++ packages/rocketchat-autolinker/autolinker.coffee
@@ -6,8 +6,24 @@
 class AutoLinker
 	constructor: (message) ->
 		if _.trim message.html
-			message.html = Autolinker.link(message.html, { stripPrefix: false, twitter: false })
-
+			# Separate text in code blocks and non code blocks
+			msgParts = message.html.split(/(```\w*[\n\ ]?[\s\S]*?```+?)/)
+
+			for part, index in msgParts
+				# Verify if this part is code
+				codeMatch = part.match(/```(\w*)[\n\ ]?([\s\S]*?)```+?/)
+				if not codeMatch?
+					msgParts[index] = Autolinker.link part,
+						stripPrefix: false
+						twitter: false
+						replaceFn: (autolinker, match) ->
+							if match.getType() is 'url'
+								return /(:\/\/|www\.).+/.test match.matchedText
+							return true
+
+			# Re-mount message
+			message.html = msgParts.join('')
+
 		return message

 RocketChat.callbacks.add 'renderMessage', AutoLinker