Better CPU code organization.

Better CPU code organization.

--- src/CPU.coffee
+++ src/CPU.coffee
@@ -407,22 +407,6 @@
         @storeValueIntoAccumulator value
         @storeValueIntoRegisterX value

-    storeValueIntoAccumulator: (value) ->
-        @updateZeroAndNegativeFlag value
-        @accumulator = value
-
-    storeValueIntoRegisterX: (value) ->
-        @updateZeroAndNegativeFlag value
-        @registerX = value
-
-    storeValueIntoRegisterY: (value) ->
-        @updateZeroAndNegativeFlag value
-        @registerY = value
-
-    storeValueIntoMemory: (address, value) ->
-        @updateZeroAndNegativeFlag value
-        @writeByte address, value
-
     ###########################################################
     # Register transfer instructions
     ###########################################################
@@ -523,14 +507,6 @@
     CPY: (address) =>
         @compareRegisterAndMemory @registerY, address

-    compareRegisterAndMemory: (register, address) ->
-        @compareRegisterAndOperand register, @readByte address
-
-    compareRegisterAndOperand: (register, operand) ->
-        result = register - operand
-        @carryFlag = result >= 0 # Unsigned comparison (bit 8 is actually the result sign).
-        @updateZeroAndNegativeFlag result # Not a signed comparison
-
     ###########################################################
     # Branching instructions
     ###########################################################
@@ -558,11 +534,6 @@

     BMI: (address) =>
         @branchIf @negativeFlag is on, address
-
-    branchIf: (condition, address) ->
-        if condition
-            @programCounter = address
-            @tick()

     ###########################################################
     # Jump / subroutine instructions
@@ -597,12 +568,59 @@
     ###########################################################

     ADC: (address) =>
-        operand = @readByte address
-        @addValueToAccumulator operand
+        @addValueToAccumulator @readByte address

     SBC: (address) =>
-        operand = @readByte address
-        @addValueToAccumulator operand ^ 0xFF # Together with carry incremment makes negative operand.
+        @addValueToAccumulator (@readByte address) ^ 0xFF # Together with carry incremment makes negative operand.
+
+    ###########################################################
+    # Shifting / rotation instructions
+    ###########################################################
+
+    ASL: (address) =>
+        @rotateAccumulatorOrMemory address, @rotateLeft, false
+
+    LSR: (address) =>
+        @rotateAccumulatorOrMemory address, @rotateRight, false
+
+    ROL: (address) =>
+        @rotateAccumulatorOrMemory address, @rotateLeft, true
+
+    ROR: (address) =>
+        @rotateAccumulatorOrMemory address, @rotateRight, true
+
+    ###########################################################
+    # Hybrid instructions
+    ###########################################################
+
+    DCP: (address) =>
+        @compareRegisterAndOperand @accumulator, @DEC address
+
+    ISB: (address) =>
+        @addValueToAccumulator (@INC address) ^ 0xFF # Together with carry incremment makes negative operand.
+
+    SLO: (address) =>
+        @storeValueIntoAccumulator @accumulator | @ASL address
+
+    ###########################################################
+    # Instruction helper functions
+    ###########################################################
+
+    storeValueIntoAccumulator: (value) ->
+        @updateZeroAndNegativeFlag value
+        @accumulator = value
+
+    storeValueIntoRegisterX: (value) ->
+        @updateZeroAndNegativeFlag value
+        @registerX = value
+
+    storeValueIntoRegisterY: (value) ->
+        @updateZeroAndNegativeFlag value
+        @registerY = value
+
+    storeValueIntoMemory: (address, value) ->
+        @updateZeroAndNegativeFlag value
+        @writeByte address, value

     addValueToAccumulator: (operand) ->
         result = @accumulator + operand
@@ -611,26 +629,22 @@
         @overflowFlag = @isSignedOverflow @accumulator, operand, result
         @storeValueIntoAccumulator result & 0xFF

-    ###########################################################
-    # Shifting / rotation instructions
-    ###########################################################
-
-    ASL: (address) =>
-        @rotateAccumulatorOrMemory address, @rotateLeft, false
-
-    LSR: (address) =>
-        @rotateAccumulatorOrMemory address, @rotateRight, false
-
-    ROL: (address) =>
-        @rotateAccumulatorOrMemory address, @rotateLeft, true
-
-    ROR: (address) =>
-        @rotateAccumulatorOrMemory address, @rotateRight, true
+    compareRegisterAndMemory: (register, address) ->
+        @compareRegisterAndOperand register, @readByte address
+
+    compareRegisterAndOperand: (register, operand) ->
+        result = register - operand
+        @carryFlag = result >= 0 # Unsigned comparison (bit 8 is actually the result sign).
+        @updateZeroAndNegativeFlag result # Not a signed comparison
+
+    branchIf: (condition, address) ->
+        if condition
+            @programCounter = address
+            @tick()

     rotateAccumulatorOrMemory: (address, rotation, transferCarry) ->
         if address?
-            operand = @readByte address
-            result = rotation operand, transferCarry
+            result = rotation (@readByte address), transferCarry
             @storeValueIntoMemory address, result
         else
             result = rotation @accumulator, transferCarry
@@ -649,26 +663,13 @@
         value |= Bit7 if transferCarry and oldCarryFlag
         value & 0xFF

-    ###########################################################
-    # Hybrid instructions
-    ###########################################################
-
-    DCP: (address) =>
-        @compareRegisterAndOperand @accumulator, @DEC address
-
-    ISB: (address) =>
-        @addValueToAccumulator (@INC address) ^ 0xFF # Together with carry incremment makes negative operand.
-
-    SLO: (address) =>
-        @storeValueIntoAccumulator @accumulator | @ASL address
-
-    ###########################################################
-    # Flags computation
-    ###########################################################
-
     updateZeroAndNegativeFlag: (value) ->
         @zeroFlag = @isZero value
         @negativeFlag = @isNegative value
+
+    ###########################################################
+    # Flags computation
+    ###########################################################

     isZero: (value) ->
         (value & 0xFF) == 0


Load commit data also from job:created

Load commit data also from job:created

--- assets/scripts/app/app.coffee
+++ assets/scripts/app/app.coffee
@@ -69,6 +69,9 @@
       [name, type] = event.split(':')

       type = Ember.get(Travis, 'mappings')[name]
+
+      if name == 'job' && data.job?.commit
+        @loadOrMerge(Travis.Commit, data.job.commit)

       if name == 'build' && data.build?.commit
         # TODO: commit should be a sideload record on build, not mixed with it


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -71,6 +71,7 @@
   return target

 ###*
+@name extension

 ###
 String::extension = ->


Reverse cell order in solvedByWeek

Reverse cell order in solvedByWeek

--- client/components/SolvedByWeek.coffee
+++ client/components/SolvedByWeek.coffee
@@ -13,7 +13,8 @@
     thisStart = new Date(startDayForWeeks["" + userList])
     now = new Date()
     nowWeek = Math.floor((now - thisStart) / MSEC_IN_WEEK)
-    [(nowWeek - lastWeeksToShow + 1)..nowWeek]
+    res = [(nowWeek - lastWeeksToShow + 1)..nowWeek]
+    res.reverse()

 bgColor = (number) ->
     if !number
@@ -41,9 +42,9 @@

 weekHeader = (weekNumber, weekCount, userList) ->
     thisStart = new Date(startDayForWeeks[userList])
-    startDay = new Date(+thisStart + MSEC_IN_WEEK * weekNumber)
-    endDay = new Date(+startDay + MSEC_IN_WEEK * weekCount - 1)
-    startDay.getUTCDate() + "." + (startDay.getUTCMonth()+1) + "-" + endDay.getUTCDate() + "." + (endDay.getUTCMonth()+1)
+    endDay = new Date(+thisStart + MSEC_IN_WEEK * (weekNumber + 1) - 1)
+    startDay = new Date(+endDay - MSEC_IN_WEEK * weekCount + 1)
+    endDay.getUTCDate() + "." + (endDay.getUTCMonth()+1) + "-" + startDay.getUTCDate() + "." + (startDay.getUTCMonth()+1)

 Header = (props) ->
     cl = props.headerClass || "h1"
@@ -99,10 +100,6 @@
 SolvedByWeekRowWithTheme = withTheme(SolvedByWeekRow)

 class SolvedByWeek extends React.PureComponent
-  componentDidMount: () ->
-    if @table_div
-        @table_div?.scrollTo 10000, 0
-
   render: () ->
     if not @props.users?.length
         return <table className={globalStyles.mainTable}/>


remove debug log

remove debug log

--- coffee/controllers/timerCtrl.coffee
+++ coffee/controllers/timerCtrl.coffee
@@ -33,7 +33,6 @@
    Initialize search form.
   ###
   initializeSearchform = () ->
-    Log.groupCollapsed "initializeSearchform()"
     $scope.ticketData =
       displayKey: 'text'
       source: util.substringMatcher(DataAdapter.tickets, 'text')
@@ -42,9 +41,6 @@
       source: util.substringMatcher(DataAdapter.activities, 'name')
       templates:
         suggestion: (n) -> "
#{n.name}#{n.id}
"
-    Log.debug DataAdapter.activities
-    Log.debug $scope.activityData
-    Log.groupEnd "initializeSearchform()"

   ###


PlayerView: clone models for EditView.

PlayerView: clone models for EditView.

--- coffee/src/views/player_view.coffee
+++ coffee/src/views/player_view.coffee
@@ -34,5 +34,8 @@
   editView: =>
     @_editView ?= new acorn.player.EditView
       eventhub: @eventhub
-      model: @model
+      model:
+        # clone models to edit safely.
+        acornModel: @model.acornModel.clone()
+        shellModel: @model.shellModel.clone()
     @_editView


Update base pod directive's action tests to instantiate directive as a void element

--- karma/test-directives.coffee
+++ karma/test-directives.coffee
@@ -127,7 +127,7 @@
         payload: {corge: 'grault'}
       }]

-      el = $compile('

')($rootScope)[0]
+      el = $compile('<cp-pod/>')($rootScope)[0]
       $rootScope.$digest()

       action1 = el.querySelectorAll('.pod-action')[0]
@@ -150,7 +150,7 @@

       $rootScope.trigger = jasmine.createSpy('trigger')

-      el = $compile('

')($rootScope)[0]
+      el = $compile('<cp-pod/>')($rootScope)[0]
       $rootScope.$digest()

       action1 = el.querySelectorAll('.pod-action')[0]


Compute added packages only after any uninstalls

--- atom/init.coffee
+++ atom/init.coffee
@@ -115,11 +115,11 @@
   # Only push if local package list differs from remote package list
   if localPkgList.join(',') isnt remotePkgList.join(',')
     console.log('Pulling package list from remote...')
+    # Uninstall local packages that are missing on remote
     removedPkgs = getArrayDiff(localPkgList, remotePkgList)
-    addedPkgs = getArrayDiff(remotePkgList, localPkgList)
-    # Uninstall local packages that are missing on remote
     uninstallPkgs(removedPkgs, ->
       # Install remote packages that are missing on local
+      addedPkgs = getArrayDiff(remotePkgList, localPkgList)
       installPkgs(addedPkgs, -> callback?())
     )
   else


Change remove-brackets default key

Change remove-brackets default key

--- keymaps/bracket-matcher.cson
+++ keymaps/bracket-matcher.cson
@@ -1,4 +1,4 @@
 '.editor':
   'ctrl-m': 'bracket-matcher:go-to-matching-bracket'
   'ctrl-cmd-m': 'bracket-matcher:select-inside-brackets'
-  ']': 'bracket-matcher:remove-brackets'
+  'ctrl-]': 'bracket-matcher:remove-brackets'


get toolbars on gridplots working again

get toolbars on gridplots working again

--- bokehjs/src/coffee/models/layouts/grid_plot.coffee
+++ bokehjs/src/coffee/models/layouts/grid_plot.coffee
@@ -182,7 +182,7 @@
     }

 class GridPlotView extends BokehView
-  className: ""
+  className: ".bk-root"
   template: plot_template

   initialize: (options) ->


support object notation in locales, ex: conversation.title

--- src/ui/app.coffee
+++ src/ui/app.coffee
@@ -35,6 +35,7 @@
 i18n.configure {
     directory: path.join __dirname, '..', 'locales'
     defaultLocale: 'en' # fallback
+    objectNotation: true
 }

 # Set locale if exists, otherwise, keep 'en'


Bugfix for last commit

Bugfix for last commit

--- app/routes/application.coffee
+++ app/routes/application.coffee
@@ -31,7 +31,7 @@
         resolve result

   setupController: (controller)->
-    if !Ember.isEmpty localStorage.userToken
+    if !Ember.isEmpty localStorage.authToken
       @fetchData()

   actions:


fix jquery issue

fix jquery issue

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -234,7 +234,7 @@

         s.popShow = () ->
             s.isPopoverVisible = true
-            popover.show("fade", "fast").focus().position
+            popover.fadeIn("fast").focus().position
                 my : my
                 at : at
                 of : elem.find(".opener")
@@ -245,7 +245,7 @@

         s.popHide = () ->
             s.isPopoverVisible = false
-            popover.hide("fade", "fast")
+            popover.fadeOut("fast")
             $rootElement.off "keydown", onEscape
             $rootElement.off "click", s.popHide
             return


feature: getVideoInfo

feature: getVideoInfo

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -3,7 +3,7 @@
 blur = require './StackBlur.js'
 youtubedl = require 'youtube-dl'
 animation = require './animation'
-
+yt = require './youtube'

 qr = (selector) -> document.querySelector selector
@@ -392,6 +392,11 @@
     @decodeVideo()

   downloadYTVideo: (url)->
+    @yt = new yt(url)
+    @yt.getVideoInfo url,(formats)=>
+      console.log 'formats',formats
+      @yt.download 133
+    return
     videoExt = @elements.videoExt
     videoFormat = @elements.videoFormat
     if url != ''


fast blur algorithm,blur box

fast blur algorithm,blur box

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -1,4 +1,7 @@
 {CompositeDisposable} = require 'atom'
+fs = require 'fs'
+blur = require './StackBlur.js'
+

 qr = (selector) -> document.querySelector selector
 style = (element) -> document.defaultView.getComputedStyle element
@@ -66,7 +69,7 @@
       order:7
     mouseFactor:
       type:"integer"
-      default: 4
+      default: 0
       description: "move background with mouse (higher value = slower)"
       order:8
     textShadow:
@@ -82,7 +85,7 @@
       order:10
     boxDepth:
       type:"integer"
-      default: 500
+      default: 0
       minimum: 0
       maximum: 2000
     boxOpacity:
@@ -100,7 +103,7 @@
     blurRadius:
       type:"integer"
       description:"0 = none"
-      default:"4"
+      default:0
       minimim:0
       maximum: 80

@@ -156,9 +159,10 @@

   mouseMove: (ev) ->
     conf=atom.config.get('editor-background')
-    @mouseX=ev.pageX
-    @mouseY=ev.pageY
-    if conf.boxDepth>0 then @updateBox() else @updateBgPos()
+    if conf.mouseFactor > 0
+      @mouseX=ev.pageX
+      @mouseY=ev.pageY
+      if conf.boxDepth>0 then @updateBox() else @updateBgPos()

   activateMouseMove: ->
@@ -181,7 +185,17 @@
     loaded = (@elements[k] for k in keys when @elements[k]?)

     if loaded.length == keys.length
+
+      @activateMouseMove()
+
       conf=atom.config.get('editor-background')
+
+      @elements.image = document.createElement 'img'
+      @elements.image.id='editor-background-image'
+      @elements.image.setAttribute 'src',conf.imageURL
+
+      @elements.blurredImage = conf.imageURL
+
       if conf.mouseFactor>0 then @activateMouseMove()
       @elements.plane = document.createElement('div')
       @elements.plane.style.cssText = planeInitialCss
@@ -189,8 +203,7 @@
       @appendCss()

       @elements.boxStyle = @createBox()
-
-      @elements.bg = document.createElement('div')
+      @elements.bg = document.createElement 'div'
       @elements.bg.style.cssText="position:absolute;width:100%;height:100%;"
       @elements.body.insertBefore @elements.bg,@elements.body.childNodes[0]

@@ -218,7 +231,7 @@
     conf=atom.config.get('editor-background')
     if not depth? then depth = conf.boxDepth
     depth2 = depth // 2
-    background=conf.imageURL
+    background=@elements.blurredImage
     opacity=(conf.boxOpacity / 100).toFixed(2)
     range=conf.boxRange
     range2=range // 3
@@ -237,6 +250,7 @@
     .eb-box-wrapper{
       perspective:1000px;
       perspective-origin:#{x}px #{y}px;
+      backface-visibility: hidden;
       position:fixed;
       top:0;
       left:0;
@@ -248,7 +262,9 @@
       transform-origin:50% 50%;
       box-shadow:inset 0px 0px #{range}px rgba(0,0,0,#{opacity}),
                   inset 0px 0px #{range2}px rgba(0,0,0,#{opacity});
-      background:url(#{background});
+      background-image:url('#{background}');
+      background-size:#{bgSize};
+      backface-visibility: hidden;
     }
     .eb-left,.eb-right{
       width:#{depth}px;
@@ -259,7 +275,7 @@
       height:#{depth}px;
     }
     .eb-left{
-      transform: translate3d(-50%,0,0) rotateY(90deg);
+      transform: translate3d(-50%,0,0) rotateY(-90deg);
       left:0;
     }
     .eb-top{
@@ -271,12 +287,11 @@
       right:0;
     }
     .eb-bottom{
-      transform: translate3d(0,50%,0) rotateX(-90deg);
+      transform: translate3d(0,50%,0) rotateX(90deg);
       bottom:0;
     }
     .eb-back{
       transform: translate3d(0,0,-#{depth2}px);
-      background-size:#{bgSize};
       width:100%;
       height:100%;
     }
@@ -284,6 +299,30 @@
     @elements.boxStyle.innerText = boxCss
     if depth==0
       @elements.boxStyle.innerText=".eb-box-wrapper{display:none;}"
+
+  blurImage:->
+    conf = atom.config.get('editor-background')
+    applyBlur = false
+    if conf.blurRadius > 0
+      if @elements.image?
+        if @elements.image.complete
+            applyBlur = true
+        else
+          setTimeout (=> @blurImage.apply @),1000
+    if applyBlur
+      console.log 'applying blurr'
+      imageData = blur.stackBlurImage @elements.image, conf.blurRadius, false
+      base64Data = imageData.replace(/^data:image\/png;base64,/, "");
+      filename = atom.packages.resolvePackagePath('editor-background')+"/blur.png"
+      fs.writeFileSync filename, base64Data,{mode:0o777,encoding:'base64'}
+      imageData=filename+'?timestamp'+Date.now();
+    else
+      imageData = conf.imageURL
+    @elements.blurredImage = imageData
+    if conf.boxDepth > 0
+      @updateBox()
+    else
+      inline @elements.bg,"background-image: url('#{imageData}') !important"

   applyBackground: ->
     atom.workspaceView.addClass 'editor-background'
@@ -308,8 +347,6 @@
         newColor = 'rgba('+rgb[0]+','+rgb[1]+','+rgb[2]+','+alpha+')'
         newTreeRGBA='rgba('+rgb[0]+','+rgb[1]+','+rgb[2]+','+treeAlpha+')'

-      bgImage = 'url('+conf.imageURL+')'
-      inline @elements.bg,'background-image:'+bgImage+' !important;'

       if conf.textShadow
         @elements.css.innerText="atom-text-editor::shadow .line{text-shadow:"+
@@ -338,10 +375,7 @@

       inline @elements.workspace,'background:'+newColor+' !important;'

-      if conf.boxDepth==0
-        inline @elements.bg,'-webkit-filter: blur('+conf.blurRadius+'px)'
-      else
-        inline @elements.bg,'-webkit-filter: blur(0px)'
+      @blurImage()

       if conf.treeViewOpacity > 0
         inline @elements.treeView,'background:'+newTreeRGBA+' !important;'


Added i18n support.

Added i18n support.

--- app/assets/javascripts/app/controllers/cti.coffee
+++ app/assets/javascripts/app/controllers/cti.coffee
@@ -49,6 +49,12 @@
       'auth'
       (data) =>
         @meta.counter = 0
+    )
+
+    # rerender view, e. g. on langauge change
+    @bind('ui:rerender', =>
+      @render()
+      'cti_rerender'
     )

   # fetch data, render view


move marked and transcribed thumbnails to end of group queue

--- app/assets/javascripts/components/group-page.cjsx
+++ app/assets/javascripts/components/group-page.cjsx
@@ -115,8 +115,23 @@
   initializeFirstPageArray: ->
     # iterate over all first pages to display and build the view
     first_pages_view = []
+    marked = []
+    transcribed = []
     for page_json, index in @state.all_first_pages
-      first_pages_view.push <FirstPageThumbnail page_json={page_json} key={index} />
+      if page_json.retired_from_mark == 1
+        marked.push page_json
+      else
+        if page_json.retired_from_transcribe == 1
+          transcribed.push page_json
+        else
+          first_pages_view.push <FirstPageThumbnail page_json={page_json} key={index} />
+
+    for page_json, index in marked
+      first_pages_view.push <FirstPageThumbnail page_json={page_json} key={"marked-" + index} />
+
+    for page_json, index in transcribed
+      first_pages_view.push <FirstPageThumbnail page_json={page_json} key={"marked-" + index} />
+
     @setState
       first_page_array: first_pages_view


Add toto marker

Add toto marker

--- app/assets/javascripts/controllers/node.js.coffee
+++ app/assets/javascripts/controllers/node.js.coffee
@@ -63,6 +63,7 @@
       promise.done (response)->
         self.set('oldWheelchair', null)
         self.get('controllers.flash-messages').pushMessage('notice', response.message)
+        # TODO: Sent event to Google Analytics

       promise.fail (response)->
         @resetStatus()


Namespaces should filter remotely

Namespaces should filter remotely

--- app/assets/javascripts/namespace_select.js.coffee
+++ app/assets/javascripts/namespace_select.js.coffee
@@ -16,6 +16,7 @@
     @dropdown.glDropdown(
       filterable: true
       selectable: true
+      filterRemote: true
       search:
         fields: ['path']
       fieldName: fieldName


Fix typo of setting to settings

Fix typo of setting to settings

Fixes an issue when using the before_add option in the S3Uploader causing a javascript error preventing upload.

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -45,7 +45,7 @@
           if $('#template-upload').length > 0
             data.context = $($.trim(tmpl("template-upload", file)))
             $(data.context).appendTo(settings.progress_bar_target || $uploadForm)
-          else if !setting.allow_multiple_files
+          else if !settings.allow_multiple_files
             data.context = settings.progress_bar_target
           if settings.click_submit_target
             if settings.allow_multiple_files


using alertify.log instead of alertify.success or alertify.error

--- app/assets/javascripts/views/login_view.js.coffee
+++ app/assets/javascripts/views/login_view.js.coffee
@@ -6,13 +6,13 @@
   loginSuccess: (user) ->
     Bandango.currentSession.logInAs user
     self.get("controller").transitionToRoute "dashboard"
-    alertify.success "Iniciaste sesión como #{user.username}"
+    alertify.log "Iniciaste sesión como #{user.username}"

   loginFailure: (response) ->
     self.setProperties
       sendingRequest: false
       error: response.responseJSON.message
-    alertify.error "Verifica tu usuario y contraseña"
+    alertify.log "Verifica tu usuario y contraseña"

   submit: ->
     self = @


Remove log line

Remove log line

--- src/window.coffee
+++ src/window.coffee
@@ -153,7 +153,6 @@
     paths: importPaths.concat(config.lessSearchPaths)
     filename: lessStylesheetPath

-  console.log importPaths.concat(config.lessSearchPaths)
   try
     content = null
     parser.parse fsUtils.read(lessStylesheetPath), (e, tree) ->


Add version to wit hubot

Add version to wit hubot

--- src/wit-ai.coffee
+++ src/wit-ai.coffee
@@ -2,7 +2,8 @@
 #   A hubot wit.ai router
 #
 # Configuration:
-#   HUBOT_WIT_TOKEN
+#   HUBOT_WIT_TOKEN=SERVER_TOKEN
+#   HUBOT_WIT_VERSION=20160527 (specify your version)
 #
 # Commands:
 #   hey hubot (hey hubot,) <dialog> - Ask me something. I may or may not understand you.
@@ -31,7 +32,7 @@
 			console.log "HUBOT_WIT_TOKEN not set"
 			return

-		robot.http("https://api.wit.ai/message?q=#{encodeURI(query)}")
+		robot.http("https://api.wit.ai/message?v=#{process.env.HUBOT_WIT_VERSION}&q=#{encodeURI(query)}")
 		.header('Content-Type', 'application/json')
 		.header('Authorization', 'Bearer ' + process.env.HUBOT_WIT_TOKEN)
 		.header('Accept', 'application/vnd.wit.20141022+json')


Update keywords on an updated article

Update keywords on an updated article

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -252,12 +252,13 @@
       return callback(err) if err
       # Merge fullscreen title with main article title
       article.title = article.hero_section.title if article.hero_section?.type is 'fullscreen'
-      if publishing
-        onPublish article, author, accessToken, sanitizeAndSave(callback)
-      else if not publishing and not article.slugs?.length > 0
-        generateSlugs article, author, sanitizeAndSave(callback)
-      else
-        sanitizeAndSave(callback)(null, article)
+      generateKeywords article, accessToken, input, (err, article) ->
+        if publishing
+          onPublish article, author, accessToken, sanitizeAndSave(callback)
+        else if not publishing and not article.slugs?.length > 0
+          generateSlugs article, author, sanitizeAndSave(callback)
+        else
+          sanitizeAndSave(callback)(null, article)

 validate = (input, callback) ->
   whitelisted = _.pick input, _.keys inputSchema
@@ -283,9 +284,7 @@
 onPublish = (article, author, accessToken, cb) ->
   if not article.published_at
     article.published_at = new Date
-  generateKeywords article, accessToken, (err, article) ->
-    return cb err if err
-    generateSlugs article, author, cb
+  generateSlugs article, author, cb

 generateSlugs = (article, author, cb) ->
   slug = _s.slugify author.name + ' ' + article.thumbnail_title
@@ -296,9 +295,15 @@
     article.slugs = (article.slugs or []).concat slug
     cb(null, article)

-generateKeywords = (article, accessToken, cb) ->
+generateKeywords = (article, accessToken, input, cb) ->
   keywords = []
   callbacks = []
+  if (input.primary_featured_artist_ids is not article.primary_featured_artist_ids or
+      input.featured_artist_ids is not article.featured_artist_ids or
+      input.fair_id is not article.fair_id or
+      input.partner_ids is not article.partner_ids or
+      input.tags is not article.tags)
+    return cb(null, article)
   if article.primary_featured_artist_ids
     for artistId in article.primary_featured_artist_ids
       do (artistId) ->
@@ -401,7 +406,6 @@
     full: url: crop(imageSrc, { width: 1200, height: 706 } )
     thumb: url: crop(imageSrc, { width: 900, height: 530 } )
   html = if article.send_body then getTextSections(article) else ''
-  console.log html
   sailthru.apiPost 'content',
     url: "#{FORCE_URL}/article/#{_.last(article.slugs)}"
     date: article.published_at
@@ -415,7 +419,6 @@
       credit_url: article.email_metadata?.credit_url
       html: html
   , (err, response) =>
-    console.log response
     debug err if err
     cb()


Fixed a couple of bugs in Logger

Fixed a couple of bugs in Logger

--- javascripts/lib/logger.coffee
+++ javascripts/lib/logger.coffee
@@ -13,15 +13,16 @@
       current_str = "#{@level}: "
       for arg in @args
         if typeof arg is 'string'
-          current_str += " #{@arg}"
+          current_str += " #{arg}"
         else
           ret.push current_str unless current_str.length == 0
           current_str = ""
           ret.push arg
+      ret.push current_str unless current_str.length == 0
       ret
     display: (force_string)->
       if force_string
-        @createStrMsg()
+        [@createStrMsg()]
       else
         @createArrMsg()

@@ -51,7 +52,7 @@
       @entries.push entry

       if @log_entry_display_types.indexOf(type) != -1
-        @console_log entry.display(@stringify_objects)
+        @console_log.apply(null, entry.display(@stringify_objects))

     typesForLevel: (level)->
       switch level


Wrap cell editors' input elements in a div

--- bokehjs/src/coffee/widget/cell_editors.coffee
+++ bokehjs/src/coffee/widget/cell_editors.coffee
@@ -18,6 +18,9 @@

   class CellEditorView extends ContinuumView

+    tagName: "div"
+    input: null
+
     emptyValue: null
     defaultValue: null

@@ -28,14 +31,16 @@
       @render()

     render: () ->
+      @$input = $(@input)
       @renderEditor()
       @disableNavigation()
+      @$el.append(@$input)
       @$el.appendTo(@args.container)

     renderEditor: () ->

     disableNavigation: () ->
-      @$el.keydown (event) =>
+      @$input.keydown (event) =>
         stop = () -> event.stopImmediatePropagation()
         switch event.keyCode
           when $.ui.keyCode.LEFT      then stop()
@@ -47,7 +52,7 @@

     destroy: () -> @remove()

-    focus: () -> @$el.focus()
+    focus: () -> @$input.focus()

     show: () ->

@@ -55,9 +60,9 @@

     position: () ->

-    getValue: () -> return @$el.val()
-
-    setValue: (val) -> @$el.val(val)
+    getValue: () -> return @$input.val()
+
+    setValue: (val) -> @$input.val(val)

     serializeValue: () -> return @getValue()

@@ -86,19 +91,19 @@

     emptyValue: ""

-    el: '<input type="text" class="bk-cell-editor bk-cell-editor-string" />'
+    input: '<input type="text" class="bk-cell-editor bk-cell-editor-string" />'

     renderEditor: () ->
       completions = @model.get("completions")
       if not _.isEmpty(completions)
-        @$el.autocomplete(source: completions)
-        @$el.autocomplete("widget").addClass("bk-cell-editor-completion")
-      @$el.focus().select()
+        @$input.autocomplete(source: completions)
+        @$input.autocomplete("widget").addClass("bk-cell-editor-completion")
+      @$input.focus().select()

     loadValue: (item) ->
       super(item)
-      @$el[0].defaultValue = @defaultValue
-      @$el.select()
+      @$input[0].defaultValue = @defaultValue
+      @$input.select()

   class StringEditor extends CellEditor
     type: 'StringEditor'
@@ -120,16 +125,16 @@

   class SelectEditorView extends CellEditorView

-    el: '<select class="bk-cell-editor bk-cell-editor-select" />'
+    input: '<select class="bk-cell-editor bk-cell-editor-select" />'

     renderEditor: () ->
       for option in @model.get("options")
-        @$el.append($('<option>').attr(value: option).text(option))
+        @$input.append($('<option>').attr(value: option).text(option))
       @focus()

     loadValue: (item) ->
       super(item)
-      @$el.select()
+      @$input.select()

   class SelectEditor extends CellEditor
     type: 'SelectEditor'
@@ -160,14 +165,14 @@

   class IntEditorView extends CellEditorView

-    el: '<input type="text" class="bk-cell-editor bk-cell-editor-int" />'
-
-    renderEditor: () ->
-      @$el.spinner(step: @model.get("step"))
-      @$el.focus().select()
+    input: '<input type="text" class="bk-cell-editor bk-cell-editor-int" />'
+
+    renderEditor: () ->
+      @$input.spinner(step: @model.get("step"))
+      @$input.focus().select()

     remove: () ->
-      @$el.spinner("destroy")
+      @$input.spinner("destroy")
       super()

     serializeValue: () ->
@@ -175,8 +180,8 @@

     loadValue: (item) ->
       super(item)
-      @$el[0].defaultValue = @defaultValue
-      @$el.select()
+      @$input[0].defaultValue = @defaultValue
+      @$input.select()

     validateValue: (value) ->
       if isNaN(value)
@@ -195,14 +200,14 @@

   class NumberEditorView extends CellEditorView

-    el: '<input type="text" class="bk-cell-editor bk-cell-editor-number" />'
-
-    renderEditor: () ->
-      @$el.spinner(step: @model.get("step"))
-      @$el.focus().select()
+    input: '<input type="text" class="bk-cell-editor bk-cell-editor-number" />'
+
+    renderEditor: () ->
+      @$input.spinner(step: @model.get("step"))
+      @$input.focus().select()

     remove: () ->
-      @$el.spinner("destroy")
+      @$input.spinner("destroy")
       super()

     serializeValue: () ->
@@ -210,8 +215,8 @@

     loadValue: (item) ->
       super(item)
-      @$el[0].defaultValue = @defaultValue
-      @$el.select()
+      @$input[0].defaultValue = @defaultValue
+      @$input.select()

     validateValue: (value) ->
       if isNaN(value)


Make compatible with JS strict mode

Make compatible with JS strict mode

--- src/xml2js.coffee
+++ src/xml2js.coffee
@@ -155,11 +155,11 @@
                 else
                   element = element.ele(key, entry).up()
               else
-                element = arguments.callee(element.ele(key), entry).up()
+                element = render(element.ele(key), entry).up()

           # Case #4 Objects
           else if typeof child is "object"
-            element = arguments.callee(element.ele(key), child).up()
+            element = render(element.ele(key), child).up()

           # Case #5 String and remaining types
           else


Remove charset from even source. Spec says it is always utf-8.

--- src/trans-eventsource.coffee
+++ src/trans-eventsource.coffee
@@ -25,7 +25,7 @@
         else
             origin = req.headers['origin']
             res.setHeader('Access-Control-Allow-Credentials', 'true')
-        res.setHeader('Content-Type', 'text/event-stream; charset=UTF-8')
+        res.setHeader('Content-Type', 'text/event-stream')
         res.setHeader('Access-Control-Allow-Origin', origin)
         res.setHeader('Vary', 'Origin')
         headers = req.headers['access-control-request-headers']


make customer chat layout_ref work again

--- app/assets/javascripts/app/controllers/layout_ref.coffee
+++ app/assets/javascripts/app/controllers/layout_ref.coffee
@@ -1676,8 +1676,7 @@
 App.Config.set( 'layout_ref/primary_email', PrimaryEmailRef, 'Routes' )

-class App.CustomerChatRef extends App.Controller
-  @extend Spine.Events
+class CustomerChatRef extends App.Controller

   questions: [
     {
@@ -1886,24 +1885,24 @@
     else
       @nextQuestion()

-class CustomerChatRouter extends App.ControllerPermanent
-  constructor: (params) ->
-    super
-
-    # check authentication
-    return if !@authenticate()
-
-    App.TaskManager.execute(
-      key:        'CustomerChatRef'
-      controller: 'CustomerChatRef'
-      params:     {}
-      show:       true
-      persistent: true
-    )
-
-#App.Config.set( 'layout_ref/customer_chat', CustomerChatRouter, 'Routes' )
-#App.Config.set( 'CustomerChatRef', { controller: 'CustomerChatRef', authentication: true }, 'permanentTask' )
-#App.Config.set( 'CustomerChatRef', { prio: 1200, parent: '', name: 'Customer Chat', target: '#layout_ref/customer_chat', key: 'CustomerChatRef', role: ['Agent'], class: 'chat' }, 'NavBar' )
+# class CustomerChatRouter extends App.ControllerPermanent
+#   constructor: (params) ->
+#     super
+
+#     # check authentication
+#     return if !@authenticate()
+
+#     App.TaskManager.execute(
+#       key:        'CustomerChatRef'
+#       controller: 'CustomerChatRef'
+#       params:     {}
+#       show:       true
+#       persistent: true
+#     )
+
+App.Config.set( 'layout_ref/customer_chat', CustomerChatRef, 'Routes' )
+# App.Config.set( 'CustomerChatRef', { controller: 'CustomerChatRef', authentication: true }, 'permanentTask' )
+# App.Config.set( 'CustomerChatRef', { prio: 1200, parent: '', name: 'Customer Chat', target: '#layout_ref/customer_chat', key: 'CustomerChatRef', role: ['Agent'], class: 'chat' }, 'NavBar' )

 class ChatWindowRef extends Spine.Controller
   @extend Spine.Events


default reading selected for ql

default reading selected for ql

--- tutor/src/components/questions/exercises-display.cjsx
+++ tutor/src/components/questions/exercises-display.cjsx
@@ -43,7 +43,7 @@
     ecosystemId: React.PropTypes.string.isRequired

   getInitialState: -> {
-    filter: ''
+    filter: 'reading'
     showingCardsFromDetailsView: false
   }
   componentWillMount:   -> ExerciseStore.on('change',  @update)


SocialWorker: JLog: remove unnecessary queue

--- workers/social/lib/social/models/log/index.coffee
+++ workers/social/lib/social/models/log/index.coffee
@@ -2,7 +2,7 @@

 module.exports = class JLog extends Module

-  { daisy } = Bongo = require 'bongo'
+  async = require 'async'

   TRY_LIMIT_FOR_BLOCKING  = 5
   TIME_LIMIT_IN_MIN       = 5
@@ -87,24 +87,15 @@

     { ip, username } = data

-    queue = [
+    return callback true  unless username

-      ->
-        return queue.next() unless username
+    options =
+      limit       : TRY_LIMIT_FOR_BLOCKING
+      sort        :
+        createdAt : -1

-        options =
-          limit       : TRY_LIMIT_FOR_BLOCKING
-          sort        :
-            createdAt : -1
-
-        JLog.some { username }, options, (err, results) ->
-          checkRestrictions err, results, (res) ->
-            unless res then return callback res
-            queue.next()
-
-      ->
+    JLog.some { username }, options, (err, results) ->
+      checkRestrictions err, results, (res) ->
+        return callback false  unless res
         return callback true

-    ]
-
-    daisy queue


Update _sidebar.coffee

Update _sidebar.coffee

fixed #63

--- coffee/_sidebar.coffee
+++ coffee/_sidebar.coffee
@@ -62,13 +62,18 @@
         menu.append new gui.MenuItem(
             label: 'Delete ' + $(@).text(),
             click: ->
-                $("#SideBar [data-name=" + playlist_name + "]").remove()
-                Playlists.delete(playlist_name)
-                Playlists.getAll((playlists) ->
-                    populateSidebar(playlists)
+                alertify.confirm('Do you want delete this playlist?', (e) ->
+                    if e
+                        $("#SideBar [data-name=" + playlist_name + "]").remove()
+                        Playlists["delete"] playlist_name
+                        Playlists.getAll (playlists) ->
+                            populateSidebar playlists
+
+                        userTracking.event("Playlist", "Delete", playlist_name).send()
                 )
-                userTracking.event("Playlist", "Delete", playlist_name).send()
+
             )
+
         menu.append new gui.MenuItem(
             label: 'Rename ' + $(@).text(),
             click: ->


Add support for PUT /{type}/{id}

Add support for PUT /{type}/{id}

--- coffee/document.coffee
+++ coffee/document.coffee
@@ -2,6 +2,7 @@

 ObjectID = require('mongodb').ObjectID
 stringify = require('JSONStream').stringify
+createHash = require('crypto').createHash

 exports.param = (req, res, next, id) ->
   return res.json 400, error: 'Ugyldig ObjectID' if not /^[a-f0-9]{24}$/.test id
@@ -51,8 +52,46 @@
     .pipe(res)

 exports.put = (req, res, next) ->
-  res.json 501, message: 'HTTP method not implmented'
-  # 200, object
+  return res.json 400, message: 'Body is missing' if Object.keys(req.body).length is 0
+  return res.json 400, message: 'Body should be a JSON Hash' if req.body instanceof Array
+
+  warnings = []
+  errors   = []
+  message  = ''
+
+  req.body.tilbyder = req.usr
+  req.body.endret = new Date().toISOString()
+  req.body.checksum = createHash('md5').update(JSON.stringify(req.body)).digest('hex')
+
+  # @TODO(starefossen) use old value?
+  if not req.body.lisens
+    req.body.lisens = 'CC BY-ND-NC 3.0 NO'
+    warnings.push
+      resource: req.col
+      field: 'lisens'
+      value: req.body.lisens
+      code: 'missing_field'
+
+  # @TODO(starefossen) use old value?
+  if not req.body.status
+    req.body.status = 'Kladd'
+    warnings.push
+      resource: req.col
+      field: 'status'
+      value: req.body.status
+      code: 'missing_field'
+
+  req.cache.getCol(req.col).save req.body, {safe: true, w: 1}, (err) ->
+    return next(err) if err
+    req.cache.setForType req.col, req.body._id, req.body, (err, data) ->
+      return next(err) if err
+      return res.json 200,
+        document:
+          _id: req.body._id
+        count: 1
+        message: message if message
+        warnings: warnings if warnings.length > 0
+        errors: errors if errors.length > 0

 exports.patch = (req, res, next) ->
   res.json 501, message: 'HTTP method not implmented'


added util.fullscreen

added util.fullscreen

--- coffee/src/util.coffee
+++ coffee/src/util.coffee
@@ -60,6 +60,16 @@
     if value?
       storedValue = validate value
     storedValue
+
+# requests full screen with given elem
+acorn.util.fullscreen = (elem) ->
+  elem = elem[0] if elem.jquery?
+  if elem.requestFullscreen
+    elem.requestFullscreen()
+  else if elem.webkitRequestFullScreen
+    elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)
+  else if elem.mozRequestFullScreen
+    elem.mozRequestFullScreen()

 # converts human-readable timeString to seconds and back
 # human-readable format is: [[hh:]mm:]ss[.SSS]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -3,8 +3,7 @@
 test "#closest", ->
   engine = Engine()

-  obj1 = engine.add
-    rad: "Awesome"
+  1 = engine.add
     x: 0
     y: 5


Avoid empty answers

Avoid empty answers

--- commands/yomama.coffee
+++ commands/yomama.coffee
@@ -621,7 +621,9 @@

 yoMama = (theme, respond) ->
     if theme?
-        respond brain.anyOf jokes.filter (j) -> j.indexOf(theme) >= 0
+        jk = jokes.filter (j) -> j.indexOf(theme) >= 0
+        if jk.length is 0 then jk = jokes
+        respond brain.anyOf jk
     else
         respond brain.anyOf jokes


attachments: correctly open google plus contents, fixes #535

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -42,11 +42,33 @@
 onclick = (e) ->
   e.preventDefault()
   address = e.currentTarget.getAttribute 'href'
+
   patt = new RegExp("^(https?[:][/][/]www[.]google[.](com|[a-z][a-z])[/]url[?]q[=])([^&]+)(&.+)*")
   if patt.test(address)
     address = address.replace(patt, '$3')
     address = unescape(address)
-  shell.openExternal fixlink(address)
+
+  finalUrl = fixlink(address)
+
+  # Google apis give us an url that is only valid for the current logged user.
+  # We can't open this url in the external browser because it may not be authenticated
+  # or may be authenticated differently (another user or multiple users).
+  # In this case we try to open the url ourselves until we get redirected to the final url
+  # of the image/video.
+  # The finalURL will be cdn-hosted, static and does not require authentication
+  # so we can finally open it in the external browser :(
+
+  xhr = new XMLHttpRequest
+
+  xhr.onreadystatechange = (e) ->
+    return if e.target.status is 0
+    return if xhr.readyState isnt 4
+    finalUrl = xhr.responseURL
+    shell.openExternal(finalUrl)
+    xhr.abort()
+
+  xhr.open("get", finalUrl)
+  xhr.send()

 # helper method to group events in time/user bunches
 groupEvents = (es, entity) ->
@@ -383,6 +405,7 @@
         return if not data
         {href, thumb} = data
     else if att?[0]?.embed_item?.type
+        console.log('THIS SHOULD NOT HAPPEN WTF !!')
         data = extractProtobufStyle(att)
         return if not data
         {href, thumb} = data
@@ -412,13 +435,18 @@
     updated 'conv'

 extractProtobufStyle = (att) ->
-    eitem = att?[0]?.embed_item
-    {plus_photo, data, type_} = eitem ? {}
+    href = null
+    thumb = null
+
+    embed_item = att?[0]?.embed_item
+    {plus_photo, data, type_} = embed_item ? {}
     if plus_photo?
+        href  = plus_photo.data?.url
         thumb = plus_photo.data?.thumbnail?.image_url
-        href  = plus_photo.data?.url
         href  = plus_photo.data?.thumbnail?.url
+        isVideo = plus_photo.data?.media_type isnt 'MEDIA_TYPE_PHOTO'
         return {href, thumb}
+
     t = type_?[0]
     return console.warn 'ignoring (old) attachment type', att unless t == 249
     k = Object.keys(data)?[0]


OX-236 added radius for blurring

OX-236 added radius for blurring

--- app/assets/javascripts/level_creator/plugins/blur.coffee
+++ app/assets/javascripts/level_creator/plugins/blur.coffee
@@ -11,59 +11,83 @@
       rgba: "Uint8Array"
     radius: "integer"

-  KERNEL = [ 1, 2, 1, 2, 4, 2, 1, 2, 1 ]

+  kernel : null
+  kernelSize : 0
+  kernelSum : 0
+
+  constructor : ->
+    @radius = 2
+    @makeKernel(@radius)

   execute : ({ input : { rgba }, radius, dimensions  }) ->

-    @dimensions = dimensions
-    @rgba = rgba
+    width = dimensions[0]
+    height = dimensions[1]

-    for i in [0...rgba.length] by 4
-      r = 0
-      g = 0
-      b = 0
+    if @radius isnt radius and typeof radius isnt "undefined"
+      @radius = radius
+      @makeKernel(radius)

-      kernelCount = 0;
+    kernel = @kernel
+    kernelSize = @kernelSize

-      #Apply kernel
-      for y in [-1..1]
-        for x in [-1..1]
-          neighbor =
-            r : @getPixel(i + 0, x, y)
-            g : @getPixel(i + 1, x, y)
-            b : @getPixel(i + 2, x, y)
+    for y in [0...height] by 1
+      for x in [0...width] by 1

-          r += KERNEL[kernelCount] * neighbor.r
-          g += KERNEL[kernelCount] * neighbor.g
-          b += KERNEL[kernelCount] * neighbor.b
-          kernelCount++
+        r = 0
+        g = 0
+        b = 0
+        a = 0

-      r /= 16
-      g /= 16
-      b /= 16
+        for j in [1 - kernelSize...kernelSize] by 1
+          if y + j < 0 or y + j >= height
+            continue

-      rgba[i + 0] = r
-      rgba[i + 1] = g
-      rgba[i + 2] = b
+          for i in [1 - kernelSize...kernelSize] by 1
+            if x + i < 0 or x + i >= width
+              continue
+
+            r += rgba[4 * ((y + j) * width + (x + i)) + 0] * kernel[Math.abs(j)][Math.abs(i)];
+            g += rgba[4 * ((y + j) * width + (x + i)) + 1] * kernel[Math.abs(j)][Math.abs(i)];
+            b += rgba[4 * ((y + j) * width + (x + i)) + 2] * kernel[Math.abs(j)][Math.abs(i)];
+            a += rgba[4 * ((y + j) * width + (x + i)) + 3] * kernel[Math.abs(j)][Math.abs(i)];
+
+        rgba[4 * (y * width + x) + 0] = r / @kernelSum;
+        rgba[4 * (y * width + x) + 1] = g / @kernelSum;
+        rgba[4 * (y * width + x) + 2] = b / @kernelSum;
+        rgba[4 * (y * width + x) + 3] = a / @kernelSum;

     rgba

-  getPixel : (_index, x, y) ->
+  makeKernel : (radius) ->

-    width = @dimensions[0]
-    height = @dimensions[1]
+    sigma = radius
+    ss = sigma * sigma
+    factor = 2 * Math.PI * ss
+    kernel = []
+    kernel.push([])

-    x *= 4
+    for i in [0...7] by 1
+      g = Math.exp(-(i * i) / (2 * ss)) / factor
+      if g < 0.001
+        break
+      kernel[0].push(g)

-    index = _index + width * 4 * y + x
+    kernelSize = i
+    for j in [1...kernelSize] by 1
+      kernel.push([])

-    #clamp
-    if index < 0
-      return 0
+      for i in [0...kernelSize] by 1
+        g = Math.exp(-(i * i + j * j) / (2 * ss)) / factor
+        kernel[j].push(g)

-    if index > height * width * 4
-      return 0
+    kernelSum = 0
+    for j in [1 - kernelSize...kernelSize] by 1
+      for i in [1 - kernelSize...kernelSize] by 1
+        kernelSum += kernel[Math.abs(j)][Math.abs(i)]

-    @rgba[ index ]
+    @kernel = kernel
+    @kernelSize = kernelSize
+    @kernelSum = kernelSum


Make it clearer that the button was pushed by not 'unpushing' it until the server responds.

--- app/assets/javascripts/backbone/views/edit.js.coffee
+++ app/assets/javascripts/backbone/views/edit.js.coffee
@@ -69,8 +69,8 @@
     e.preventDefault()
     btn = $(e.currentTarget)
     btn.button 'loading'
-    $.post(@model.build_url())
-    btn.button 'reset'
+    $.post @model.build_url(), {}, () ->
+      btn.button 'reset'

   render: ->


Rename resource-combiner resourceCache to idResourceCache

--- lib/resource-combiner.coffee
+++ lib/resource-combiner.coffee
@@ -1,15 +1,15 @@
 resourceRequest = require './resource-request.coffee'
 virtual = require './virtual-resource.coffee'

-resourceCache = {}
+idResourceCache = {}

 # TODO : Implement cache invalidation on a periodic basis here
 exports.combine = ({resources, req, separator, contentType, prefix}) ->
   id = resources.length + (resource.href for resource in resources).join ':'

-  if not resource = resourceCache[id]
+  if not resource = idResourceCache[id]
     # Create a new resource and load async
-    resource = resourceCache[id] = {
+    resource = idResourceCache[id] = {
       id
       content: {href: resource.href, complete: false} for resource in resources
       deferred: []


:boar: Remove test task

:boar: Remove test task

--- grunt/babel.coffee
+++ grunt/babel.coffee
@@ -20,12 +20,3 @@
       src: ['**/*.js'],
       dest: 'dist/common/'
     }]
-  test:
-    options:
-      modules: 'amd'
-    files: [{
-      expand: true,
-      cwd: 'test/src'
-      src: ['**/*.js']
-      dest: 'test/spec'
-    }]


Fixed one-click checkout, should transition to payment state

--- app/assets/javascripts/sprangular/directives/checkoutButton.coffee
+++ app/assets/javascripts/sprangular/directives/checkoutButton.coffee
@@ -25,7 +25,7 @@
       order.resetAddresses(user)
       order.resetCreditCard(user)

-      Checkout.update()
+      Checkout.update('payment')
         .success ->
           $location.path('/checkout/confirm')
         .error ->


Little less default tolerance on bounce/momentum

--- framer/Defaults.coffee
+++ framer/Defaults.coffee
@@ -52,11 +52,11 @@
 	MomentumBounceSimulator:
 		momentum:
 			friction: 2
-			tolerance: 1/10
+			tolerance: 10
 		bounce:
 			tension: 500
 			friction: 10
-			tolerance: 1/10000
+			tolerance: 1

 exports.Defaults =


Updated closing tab to point to prototype method

--- app/assets/javascripts/lib/components/tabs.js.coffee
+++ app/assets/javascripts/lib/components/tabs.js.coffee
@@ -35,7 +35,7 @@
       tabs.on 'click', '.js-tab-item', (e) ->
         tabLabel = $(@)
         tab = $(tabLabel.attr('href'))
-        if tabLabel.hasClass('active') then @closeTabs() else openNewTab(tabLabel, tab)
+        if tabLabel.hasClass('active') then Tabs::closeTabs() else openNewTab(tabLabel, tab)
         false


Try to fix assets in production.

Try to fix assets in production.

--- app/assets/javascripts/try_api/application.js.coffee
+++ app/assets/javascripts/try_api/application.js.coffee
@@ -2,9 +2,9 @@
 #= require try_api/bower_components/bootstrap/dist/js/bootstrap.min.js
 #= require try_api/bower_components/jquery-slimscroll/jquery.slimscroll.min.js
 #= require try_api/bower_components/highlightjs/highlight.pack.min.js
+#= require try_api/bower_components/angular-highlightjs/src/angular-highlightjs.min.js
 #= require try_api/bower_components/angular/angular.js
 #= require try_api/bower_components/angular-bootstrap/ui-bootstrap-tpls.js
-#= require try_api/bower_components/angular-highlightjs/src/angular-highlightjs.js

 $ ->
   $('pre code').each (i, block) ->
@@ -13,6 +13,3 @@

   $('.try-api-sidebar-menu').slimScroll
     height: '100%'
-
-
-


new integration spec

new integration spec

--- test/integrationSpec.coffee
+++ test/integrationSpec.coffee
@@ -105,7 +105,7 @@

       JE.run(context)

-    it 'dependencies with inner describe part 1', ->
+    fit 'dependencies with inner describe part 1', ->
       context.describe 'test3', ->
         another   .is collection + ' another'
         third     .is 'Third'
@@ -128,6 +128,60 @@
             expect(third).toBeEqual 'Third'
             expect(collection).toBeEqual 'something else'
             expect(another).toBeEqual 'something else another'
+
+      JE.run(context)
+
+    xit 'dependencies with inner describe part 2', ->
+      context.describe 'test4', ->
+        another   .is collection + ' another'
+        third     .is 'Third'
+
+        @it 'one', ->
+          console.log 'in it'
+          expect(third).toBeEqual 'Third'
+          expect(collection).toBeUndefined()
+          expect(another).toBeUndefined()
+          expect(fourth).toBeUndefined()
+          expect(fifth).toBeUndefined()
+
+        @describe 'inner_first', ->
+          collection.is 'something else one'
+          fourth.is another + ' fourth'
+
+          @it 'two', ->
+            console.log 'in inner it'
+            console.log another
+            expect(third).toBeEqual 'Third'
+            expect(collection).toBeEqual 'something else one'
+            expect(another).toBeEqual 'something else one another'
+            expect(fourth).toBeEqual 'something else one another fourth'
+            expect(fifth).toBeUndefined()
+
+          @it 'three', ->
+            expect(third).toBeEqual 'Third'
+            expect(collection).toBeEqual 'something else one'
+            expect(another).toBeEqual 'something else one another'
+            expect(fourth).toBeEqual 'something else one another fourth'
+            expect(fifth).toBeUndefined()
+
+        @describe 'inner_second', ->
+          collection.is 'something else second'
+          fifth.is another + ' fifth'
+
+          @it 'fourth', ->
+            console.log another
+            expect(third).toBeEqual 'Third'
+            expect(collection).toBeEqual 'something else second'
+            expect(another).toBeEqual 'something else second another'
+            expect(fifth).toBeEqual 'something else second another fifth'
+            expect(fourth).toBeUndefined()
+
+          @it 'fifth', ->
+            expect(third).toBeEqual 'Third'
+            expect(collection).toBeEqual 'something else second'
+            expect(another).toBeEqual 'something else second another'
+            expect(fifth).toBeEqual 'something else second another fifth'
+            expect(fourth).toBeUndefined()

       JE.run(context)


Fixed bug in getting views for entity with no _views override.

--- js/binding/src/binding.coffee
+++ js/binding/src/binding.coffee
@@ -104,6 +104,9 @@
 			_property = _entity.find("#" + property)
 			if _property.length isnt 1
 				_property = _entity.children("#_default_property")
+				#If there STILL isn't a property, fall back to using `_default_entity`
+				if _property.length isnt 1
+					_property = find.property(root, theme, "_default_entity", property)
 			_property.clone()

 		view: (root = settings.paths.root, theme = settings.paths.theme, entity = "_default_entity", property = "?", view = "view") ->


fix update to work with redis cluster

fix update to work with redis cluster

the items do not have the same tag, so we must delete the entry from the
docsWithHistoryOps set outside the multi.

--- app/coffee/RedisManager.coffee
+++ app/coffee/RedisManager.coffee
@@ -21,12 +21,13 @@
 		# Delete all the updates which have been applied (exact match)
 		for update in docUpdates or []
 			multi.lrem Keys.uncompressedHistoryOps({doc_id}), 0, update
-		# It's ok to delete the doc_id from the set here. Even though the list
-		# of updates may not be empty, we will continue to process it until it is.
-		multi.srem Keys.docsWithHistoryOps({project_id}), doc_id
 		multi.exec (error, results) ->
 			return callback(error) if error?
-			callback null
+			# It's ok to delete the doc_id from the set here. Even though the list
+			# of updates may not be empty, we will continue to process it until it is.
+			rclient.srem Keys.docsWithHistoryOps({project_id}), doc_id, (error) ->
+				return callback(error) if error?
+				callback null

 	getDocIdsWithHistoryOps: (project_id, callback = (error, doc_ids) ->) ->
 		rclient.smembers Keys.docsWithHistoryOps({project_id}), callback


Removed the space in Modal module

Removed the space in Modal module

--- lib/Modules/src/modal.coffee
+++ lib/Modules/src/modal.coffee
@@ -1,7 +1,7 @@
 class TocasModal
     $name:
         'modal'
-
+
     $options:
         approve          : '.approve, .positive, .ok'  # 會呼叫同意回呼函式的元素選擇器。
         deny             : '.deny, .negative, .cancel' # 會呼叫拒絕回呼函式的元素選擇器。


cascade: simplify should usage in test

cascade: simplify should usage in test

--- test/options/cascade.coffee
+++ test/options/cascade.coffee
@@ -4,20 +4,20 @@

 describe 'options "cascade"', ->

-  describe 'global', ->
+  describe 'session', ->

     it 'propagate option', ->
       context.cascade.my_global_option = true
       n = nikita
       n
       .call ->
-        n.cascade.my_global_option.should.be.true()
+        n.cascade.my_global_option.should.true()
       .call my_global_option: 'value', (options) ->
-        options.my_global_option.should.be.equal 'value'
+        options.my_global_option.should.equal 'value'
         @call (options) ->
-          options.my_global_option.should.be.equal 'value'
+          options.my_global_option.should.equal 'value'
           @call (options) ->
-            options.my_global_option.should.be.equal 'value'
+            options.my_global_option.should.equal 'value'
       .call ->
         delete context.cascade.my_global_option
       .promise()
@@ -44,7 +44,7 @@
         parent_param_uncascaded: true
       .promise()

-  describe 'context', ->
+  describe 'action', ->

     it 'propagate option', ->
       n = nikita
@@ -53,39 +53,39 @@
       .call ->
         n.cascade.my_context_option.should.be.true()
       .call my_context_option: 'value', (options) ->
-        options.my_context_option.should.be.equal 'value'
+        options.my_context_option.should.equal 'value'
         @call (options) ->
-          options.my_context_option.should.be.equal 'value'
+          options.my_context_option.should.equal 'value'
           @call (options) ->
-            options.my_context_option.should.be.equal 'value'
+            options.my_context_option.should.equal 'value'
       .call ->
         n.cascade.my_context_option.should.be.true()
       .promise()

-  it 'dont cascade context options', ->
-    n = nikita
-    n
-    .call ->
-      n.cascade.header.should.be.false()
-    .call header: 'h1', (options) ->
-      (options.header is undefined).should.be.true()
-      @call header: 'h2', (options) ->
+    it 'dont cascade context options', ->
+      n = nikita
+      n
+      .call ->
+        n.cascade.header.should.be.false()
+      .call header: 'h1', (options) ->
         (options.header is undefined).should.be.true()
-    .promise()
-
-  it 'can be disabled in action', ->
-    n = nikita
-    n
-      cascade: a_key: true
-      a_key: 'a value'
-    .call (options) ->
-      options.a_key.should.eql 'a value'
-    .call a_key: null, (options) ->
-      (options.a_key is null).should.be.true()
-      @call (options) ->
+        @call header: 'h2', (options) ->
+          (options.header is undefined).should.be.true()
+      .promise()
+
+    it 'can be disabled in action', ->
+      n = nikita
+      n
+        cascade: a_key: true
+        a_key: 'a value'
+      .call (options) ->
+        options.a_key.should.eql 'a value'
+      .call a_key: null, (options) ->
         (options.a_key is null).should.be.true()
         @call (options) ->
           (options.a_key is null).should.be.true()
-    .call (options) ->
-      options.a_key.should.eql 'a value'
-    .promise()
+          @call (options) ->
+            (options.a_key is null).should.be.true()
+      .call (options) ->
+        options.a_key.should.eql 'a value'
+      .promise()


Remove Webpack devtool configuration (not allowed for Chrome extensions)

--- src/webpackConfigBase.coffee
+++ src/webpackConfigBase.coffee
@@ -11,7 +11,7 @@
     new webpack.ContextReplacementPlugin /moment[\\\/]locale$/, new RegExp ".[\\\/](#{LANGS.join '|'})"
   ]

-  devtool: if process.env.NODE_ENV isnt 'production' then 'eval'
+  ## devtool: if process.env.NODE_ENV isnt 'production' then 'eval'

   module:
     loaders: [


Hide the action buttons instead of just disabling them

--- app/controllers/actions.coffee
+++ app/controllers/actions.coffee
@@ -28,7 +28,7 @@
     show @action_buttons

   finished: =>
-    disable @action_buttons
+    hide @action_buttons
     show @continue_button

   # Private


Remove old version

Remove old version

--- src/app.coffee
+++ src/app.coffee
@@ -1,125 +0,0 @@
-
-parties = [
-  { name: 'Partido A', cssName: 'a-party', votes: 100000 },
-  { name: 'Partido B', cssName: 'b-party', votes:  80000 },
-  { name: 'Partido C', cssName: 'c-party', votes:  30000 },
-  { name: 'Partido D', cssName: 'd-party', votes:  20000 },
-  { name: 'Partido E', cssName: 'd-party', votes:  20 },
-]
-
-totalVotes = _.sum(_.pluck(parties, 'votes'))
-
-simulatePrice = (price) ->
-  _.map(parties, (p) -> Math.ceil(p.votes / price))
-
-simulation = [simulatePrice(price) for price in [1000..totalVotes] by 1000]
-
-width = 500
-height = 300
-chart = d3.select(".chart")
-  .attr("width", width)
-  .attr('height', height)
-  .attr('font-family', 'Arial')
-
-labelWidth = 100
-scaleWidth = width - labelWidth
-x = d3.scale.linear()
-  .domain([0, d3.max(parties, (p) -> p.votes)])
-  .range([0, scaleWidth])
-
-y = d3.scale.ordinal()
-  .domain(_.map(parties, 'name'))
-  .rangeRoundBands([0, height], 0.3, 0.2)
-
-colors = d3.scale.category10().range()
-
-setBarText = (bar) ->
-  bar
-    .text((d) -> (d.votes * 100 / totalVotes).toFixed(2) + '%')
-    .attr('x', (d) ->
-      if x(d.votes) > 50 # big enough bar, put text inside
-        labelWidth + x(d.votes) - 5
-      else #small bar, put text aside
-        labelWidth + x(d.votes) + 5
-    )
-    .attr('text-anchor', (d) ->
-      if x(d.votes) > 50 # big enough bar, put text inside
-        'end' # align right
-      else #small bar, put text aside
-        'start' # align left
-    )
-    .attr('fill', (d, i) ->
-      if x(d.votes) > 50
-        'white'
-      else
-        'black'
-    )
-
-build = (bar) ->
-  bar = bar.append('g')
-    .attr('transform', (d, i) -> "translate(0,#{y.range()[i]})")
-
-  bar.append('rect')
-    .attr('height', y.rangeBand())
-    .attr('x', labelWidth)
-    .style('fill', (d, i) -> colors[i])
-
-  bar.append('text')
-    .attr('class', 'ylabel')
-    .attr('x', labelWidth - 10)
-    .attr('y', y.rangeBand() / 2)
-    .attr('text-anchor', 'end')
-    .attr('dy', '.35em')
-    .style('fill', 'black')
-    .text((d) -> d.name)
-
-  bar.append('text')
-    .attr('class', 'text-value')
-    .attr('y', y.rangeBand() / 2)
-    .attr('dy', '.35em')
-    .attr('opacity', 1)
-
-update = (bar) ->
-  bar.selectAll('rect')
-    .transition()
-      .duration(500)
-      .attr('width', (d) -> x(d.votes))
-
-  bar.selectAll('.text-value')
-    .transition()
-      .duration(100)
-      .attr('opacity', 0)
-    .transition()
-      .call(setBarText)
-    .transition()
-      .delay(300)
-      .duration(100)
-      .attr('opacity', 1)
-drawBarChart = ->
-  sel = chart.selectAll('g')
-    .data(parties, (d) -> d.name)
-
-  sel.enter().call(build)
-
-  sel.call(update)
-  return
-
-  completeBar(updateSet)
-
-  insertSet = updateSet.enter()
-    .append('g')
-  completeBar(insertSet)
-
-  deleteSet = updateSet.exit()
-    .remove()
-
-  return
-
-
-randomize = ->
-  for party in parties
-    party.votes = Math.random() * 50000
-  drawBarChart()
-
-drawBarChart()
-setInterval randomize, 2000


Marionette region updated API.

Marionette region updated API.

--- src/app.coffee
+++ src/app.coffee
@@ -253,7 +253,7 @@

         closeFeedback: ->
             @_resetPendingFeedback()
-            _.defer => app.getRegion('feedbackFormContainer').reset()
+            _.defer => app.getRegion('feedbackFormContainer').empty()

         showServiceMapDescription: ->
             app.getRegion('feedbackFormContainer').show new disclaimers.ServiceMapDisclaimersView()


add typescript go to decleration

add typescript go to decleration

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -50,19 +50,26 @@
     symbolLocation = @index[selection]
     if symbolLocation and selection
       atom.workspace.open(symbolLocation)
+    else
+      atom.commands.dispatch(document.querySelector('atom-text-editor'), 'typescript:go-to-declaration')
+

   addImportStatement: (importStatement) ->
       editor = atom.workspace.getActiveTextEditor()
       currentText = editor.getText()
       currentPosition = editor.getCursorBufferPosition()
-      importMatches = currentText.match(/import\s*\w*\s*from.*\n/)
+      importMatches = currentText.match(/import\s*\w*\s*from.*\n/g)
       referencesMatches= currentText.match(/\/\/\/\s*<reference\s*path.*\/>\n/g)
+      useStrictMatche = currentText.match(/.*[\'\"]use strict[\'\"].*/)
       if importMatches
         lastImport = importMatches.pop();
         currentText = currentText.replace(lastImport, lastImport + importStatement);
       else if referencesMatches
         lastReference = referencesMatches.pop();
         currentText = currentText.replace(lastReference, lastReference + '\n' + importStatement);
+      else if useStrictMatche
+        useStrict = useStrictMatche.pop();
+        currentText = currentText.replace(useStrict, useStrict + '\n' + importStatement);
       else
         currentText = importStatement + currentText;
       editor.setText(currentText);


Automatically add new words to the wordlist.

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -2,7 +2,7 @@
 path = require 'path'
 minimatch = require 'minimatch'
 SimpleSelectListView = require './simple-select-list-view'
-{Editor, $, $$, Range, SelectListView}  = require 'atom'
+{Editor, $, $$, Range, Point, SelectListView}  = require 'atom'
 fuzzaldrin = require 'fuzzaldrin'
 Perf = require './perf'
 Q = require 'q'
@@ -256,6 +256,32 @@
     return prefix

   ###
+   * Finds the last typed word. If newLine is set to true, it looks
+   * for the last word in the previous line.
+  ###
+  lastTypedWord: (newLine) ->
+    selectionRange = @editor.getSelection().getBufferRange()
+    {row} = selectionRange.start
+
+    # The user pressed enter, check previous line
+    if newLine
+      row--
+
+    # The user presed enter, check everything until the end
+    if newLine
+      maxColumn = @editor.lineLengthForBufferRow row
+    else
+      maxColumn = selectionRange.start.column
+
+    lineRange = [[row, 0], [row, maxColumn]]
+
+    lastWord = null
+    @currentBuffer.scanInRange @wordRegex, lineRange, ({match, range, stop}) ->
+      lastWord = match[0]
+
+    return lastWord
+
+  ###
    * As soon as the list is in the DOM tree, it calculates the
    * maximum width of all list items and resizes the list so that
    * all items fit
@@ -294,12 +320,25 @@

     if atom.config.get('autocomplete-plus.liveCompletion')
       @currentBuffer.on "changed", (e) =>
+        if e.newText in ["\n", " "]
+          @addLastWordToList e.newText is "\n"
+
         if e.newText.length
           @contentsModified()
         else
           @cancel()

   ###
+   * Adds the last typed word to the wordList
+  ###
+  addLastWordToList: (newLine) ->
+    lastWord = @lastTypedWord newLine
+    return unless lastWord
+
+    if @wordList.indexOf(lastWord) < 0
+      @wordList.push lastWord
+
+  ###
    * Defines which key we would like to use for filtering
   ###
   getFilterKey: -> 'word'


minor fix

minor fix

--- lib/main.coffee
+++ lib/main.coffee
@@ -172,17 +172,17 @@
         dest[element] = {} unless dest.hasOwnProperty(element)
         arguments.callee(destination, ary, path, counter_name, (index+1))

-      output = file_utils.readDirSync(dir)
-      if output
-        slashes_to_remove = dir.split('/').length
-        check.forNameConflicts(output)
-        output.files.forEach (path) ->
-
-          ary = path.split('/').slice(slashes_to_remove)
-          mod_name = ary.pop()
-          ary.push(mod_name.split('.')[0])
-
-          recursively($SS[name], ary, path, name)
+    output = file_utils.readDirSync(dir)
+    if output
+      slashes_to_remove = dir.split('/').length
+      check.forNameConflicts(output)
+      output.files.forEach (path) ->
+
+        ary = path.split('/').slice(slashes_to_remove)
+        mod_name = ary.pop()
+        ary.push(mod_name.split('.')[0])
+
+        recursively($SS[name], ary, path, name)

   # Load any vendored modules


Reworking the init sequence (still broken)

--- static/js/maproulette.coffee
+++ static/js/maproulette.coffee
@@ -256,10 +256,13 @@
       setDelay 3, msgClose
     msgTaskText()

-@getNextTask = (near = null) ->
-  ###
-  # Gets another task from the current challenge, close to the location (if supplied)
-  ###
+@getTaskByChallenge = (challenge, near = null) ->
+  ###
+  # Gets another task from the current challenge, close to the
+  # location (if supplied)
+  ###
+    if not currentChallenge?
+      #
     if near
       url = "/c/#{currentChallenge.slug}/task?near=#{near}"
     else
@@ -464,6 +467,7 @@
       tileURL = data.tileurl
       tileAttrib = data.tileasttribution if data.tileattribution?
       initmap()
+
 @init = ->
   ###
   # Find a challenge and set the map up
@@ -497,12 +501,16 @@
         when "i"
           openIn('id')

-
-  # Determine the user's preferences
+  # Try to grab parameters from the url
   challenge = $(document).getUrlParam("challenge")
-  if
-  difficulty = "beginner" if not difficulty?
-  if not location?
+  difficulty = $(document).getUrlParam("difficulty")
+  near = $(document).getUrlParam("near")
+  difficulty = "easy" if not difficulty?
+  if challenge?
+    # If we know the challenge, we can use that to set the page up
+  else
+    # We'll need to grab a task and then use the information from that
+    # task to populate the page

   # Now make a request for a task


update zh-HANS.coffee (#5559)

update zh-HANS.coffee (#5559)

* Update zh-HANS.coffee

* Update zh-HANS.coffee

* Update zh-HANS.coffee

* update

* update

--- app/locale/zh-HANS.coco.coffee
+++ app/locale/zh-HANS.coco.coffee
@@ -170,7 +170,7 @@
     legal: "版权声明"
     privacy: "隐私"
     about: "关于"
-#    impact: "Impact"
+    impact: "影响力"
     contact: "联系我们"
     twitter_follow: "关注"
     my_classrooms: "我的班级"
@@ -741,8 +741,8 @@
     methods: "函数"
     events: "事件"
 #    handlers: "Handlers"
-#    properties: "Properties"
-#    snippets: "Snippets"
+    properties: "属性"
+    snippets: "字段"
     spawnable: "可生成物"
     html: "HTML"
     math: "数学"
@@ -1068,7 +1068,7 @@
     learn_more: "了解更多"
     main_title: "学习编程需要动手写大量代码"
     main_description: "CodeCombat让你带着微笑学习"
-    mission_link: "任务"
+    mission_link: "愿景"
     team_link: "团队"
     story_link: "历程"
     press_link: "联系方式"
@@ -1076,7 +1076,7 @@
     mission_description_1: "编程是一种魔法。这是一种从想象中创造万物的能力。我们开始了 CodeCombat 这个项目，是为了让学习者通过编写代码感受到他们指尖上的魔力。"
     mission_description_2: "同时，他们也能迅速学习，因为学习的方式变快了。学习就像是对话而不是阅读枯燥的手册。我们想将这种对话带给每所学校和每个学生，因为每个人都应有机会学习编程的魔法。"
     team_title: "与CodeCombat团队见面"
-    team_values: "我们期待开放的、互相尊敬的对话，好点子会得到采纳。我们决定深入市场调研，我们集中于提供实际性的结果。从我们的 CEO 到 Github 上的贡献者，每个人都亲历亲为，因为在团队中我们收获了成长与经验。"
+    team_values: "我们期待开放的、互相尊敬的对话，好点子会得到采纳。我们决定深入市场调研，我们集中于提供实际性的结果。从我们的 CEO 到 Github 上的贡献者，每个人都亲力亲为，因为在团队中我们收获了成长与经验。"
 #    nick_title: "Cofounder, CEO"
 #    matt_title: "Cofounder, CTO"
 #    cat_title: "Game Designer"
@@ -2407,58 +2407,58 @@
 #    level_options: "Level Options"
 #    restart_level: "Restart Level"

-#  impact:
-#    hero_heading: "Building A World-Class Computer Science Program"
-#    hero_subheading: "We Help Empower Educators and Inspire Students Across the Country"
-#    featured_partner_story: "Featured Partner Story"
-#    partner_heading: "Successfully Teaching Coding at a Title I School"
-#    partner_school: "Bobby Duke Middle School"
-#    featured_teacher: "Scott Baily"
-#    teacher_title: "Technology Teacher Coachella, CA"
-#    implementation: "Implementation"
-#    grades_taught: "Grades Taught"
-#    length_use: "Length of Use"
-#    length_use_time: "3 years"
-#    students_enrolled: "Students Enrolled this Year"
-#    students_enrolled_number: "130"
-#    courses_covered: "Courses Covered"
-#    course1: "CompSci 1"
-#    course2: "CompSci 2"
-#    course3: "CompSci 3"
-#    course4: "CompSci 4"
-#    course5: "GameDev 1"
-#    fav_features: "Favorite Features"
-#    responsive_support: "Responsive Support"
-#    immediate_engagement: "Immediate Engagement"
-#    paragraph1: "Bobby Duke Middle School sits nestled between the \t    Southern California mountains of Coachella Valley to the west and east and the Salton Sea 33 miles south, and boasts a student population of 697 students within Coachella Valley Unified’s district-wide population of 18,861 students."
-#    paragraph2: "The students of Bobby Duke Middle School reflect the socioeconomic challenges facing Coachella Valley’s residents and students within the district. With over 95% of the Bobby Duke Middle School student population qualifying for free and reduced-price meals and over 40% classified as English language learners , the importance of teaching 21st century skills was the top priority of Bobby Duke Middle School Technology teacher, Scott Baily."
-#    paragraph3: "Baily knew that teaching his students coding was a key pathway to opportunity in a job landscape that increasingly prioritizes and necessitates computing skills. So, he decided to take on the exciting challenge of creating and teaching the only coding class in the school and finding a solution that was affordable, responsive to feedback, and engaging to students of all learning abilities and backgrounds."
-#    teacher_quote: "When I got my hands on CodeCombat [and] started having my students use it, the light bulb went on. It was just night and day from every other program that we had used. They’re not even close."
-#    quote_attribution: "Scott Baily, Technology Teacher"
-#    read_full_story: "Read Full Story"
-#    more_stories: "More Partner Stories"
-#    partners_heading_1: "Supporting Multiple CS Pathways in One Class"
-#    partners_school_1: "Preston High School"
-#    partners_heading_2: "Excelling on the AP Exam"
-#    partners_school_2: "River Ridge High School"
-#    partners_heading_3: "Teaching Computer Science Without Prior Experience"
-#    partners_school_3: "Riverdale High School"
-#    download_study: "Download Research Study"
-#    teacher_spotlight: "Teacher & Student Spotlights"
-#    teacher_name_1: "Amanda Henry"
-#    teacher_title_1: "Rehabilitation Instructor"
-#    teacher_location_1: "Morehead, Kentucky"
-#    spotlight_1: "Through her compassion and drive to help those who need second chances, Amanda Henry helped change the lives of students who need positive role models. With no previous computer science experience, Henry led her students to coding success in a regional coding competition."
-#    teacher_name_2: "Kaila, Student"
-#    teacher_title_2: "Maysville Community & Technical College"
-#    teacher_location_2: "Lexington, Kentucky"
-#    spotlight_2: "Kaila was a student who never thought she would be writing lines of code, let alone enrolled in college with a pathway to a bright future."
-#    teacher_name_3: "Susan Jones-Szabo"
-#    teacher_title_3: "Teacher Librarian"
-#    teacher_school_3: "Ruby Bridges Elementary"
-#    teacher_location_3: "Alameda, CA"
-#    spotlight_3: "Susan Jones-Szabo promotes an equitable atmosphere in her class where everyone can find success in their own way. Mistakes and struggles are welcomed because everyone learns from a challenge, even the teacher."
-#    continue_reading_blog: "Continue Reading on Blog..."
+  impact:
+    hero_heading: "创建世界一流的计算机科学课程"
+    hero_subheading: "我们赋能教育工作者，激励全球学习者"
+    featured_partner_story: "合作伙伴故事"
+    partner_heading: "美国最好的中学之一成功开展编程教学"
+    partner_school: "Bobby Duke 中学"
+    featured_teacher: "Scott Baily"
+    teacher_title: "信息技术老师, 科切拉谷，加利福尼亚"
+    implementation: "总体情况"
+    grades_taught: "面向年级"
+    length_use: "使用时期"
+    length_use_time: "3年"
+    students_enrolled: "今年报名的学生"
+    students_enrolled_number: "130"
+    courses_covered: "讲授课程"
+    course1: "计算机科学 1"
+    course2: "计算机科学 2"
+    course3: "计算机科学 3"
+    course4: "计算机科学 4"
+    course5: "游戏开发 1"
+    fav_features: "最爱功能"
+    responsive_support: "即时支持"
+    immediate_engagement: "场景交互"
+    paragraph1: "Bobby Duke 中学坐落在南加州科切拉谷山脉东西向，距离萨尔顿海33英里以南，共有697名学生。"
+    paragraph2: "Bobby Duke 中学的情况反映了科切拉谷居民和学生在社区内面临的社会经济挑战。学校里95%以上的中学生符合免费或补助餐食条件，超过40%的学生被归类为英语学习者，因此讲授21世纪技能是 Bobby Duke 中学的首要任务，信息技术老师 Scott Baily 说道。"
+    paragraph3: "Baily 深刻了解到在如今优先考虑和计算技能成为刚需的工作环境中，教会学生编写代码是获得机会的关键途径。因此，他决定接受一个令人兴奋的挑战，创建学校里唯一一门编程课程，并找到一个经济上能够负担，内容上即时反馈，并能吸引不同学习能力及不同背景学生们的解决方案。"
+    teacher_quote: "当我开始接触 CodeCombat 并开始让我的学生使用它时，就像点亮了我脑子里的一盏灯一样。这与与我接触的其他编程产品有着日与夜的差别，没有比 CodeCombat 更好的产品。"
+    quote_attribution: "Scott Baily, 信息技术老师"
+    read_full_story: "阅读全文"
+    more_stories: "更多合作伙伴故事"
+    partners_heading_1: "在一个班级中实现多个计算机科学课程的开展"
+    partners_school_1: "Preston 高中"
+    partners_heading_2: "在AP考试中脱颖而出 "
+    partners_school_2: "River Ridge 高中"
+    partners_heading_3: "零经验讲授计算机科学"
+    partners_school_3: "Riverdale 高中"
+    download_study: "下载研究报告"
+    teacher_spotlight: "老师和学生们的高光时刻"
+    teacher_name_1: "Amanda Henry"
+    teacher_title_1: "康复指导员"
+    teacher_location_1: "莫尔黑德，肯塔基州"
+    spotlight_1: "凭着一心想要帮助那些急需第二次机会的人的热情和动力，阿曼达改变了这群需要积极榜样的学生的生活。没有计算机科学经验的她，带领学生在区域编程竞赛中取得了成功。"
+    teacher_name_2: "Kaila, 学生"
+    teacher_title_2: "梅斯维尔社区与技术学院 列克星敦，肯塔基州"
+    teacher_location_2: "Lexington, Kentucky"
+    spotlight_2: "凯拉是一个从未想过会敲打代码的学生，更不用说考入通往光明未来的大学。"
+    teacher_name_3: "Susan Jones-Szabo"
+    teacher_title_3: "教师图书馆员"
+    teacher_school_3: "Ruby Bridges 小学"
+    teacher_location_3: "阿拉米达，加利福尼亚州"
+    spotlight_3: "苏珊在她的班级中提倡一种公平的氛围，每个人都能以自己的方式成功。犯错和挣扎也是受欢迎的，因为每个人都能从挑战中学习，包括老师。"
+    continue_reading_blog: "在博客中继续阅读..."

   loading_error:
     could_not_load: "载入失败" # {change}


more descriptive callback cancellation

more descriptive callback cancellation

--- lib/connection/client.coffee
+++ lib/connection/client.coffee
@@ -36,7 +36,7 @@
         @loading.done()

     @handle 'cancelCallback', (id) =>
-      @callbacks[id].reject()
+      @callbacks[id].reject "cancelled by julia"
       @loading.done()

   # Will be replaced by the connection logic
@@ -109,7 +109,7 @@
     @cancelBoot()
     @loading.reset()
     @queue = []
-    cb.reject() for id, cb of @callbacks
+    cb.reject 'client disconnected' for id, cb of @callbacks
     @callbacks = {}

   isWorking: -> @loading.isWorking()


add serialize attrs to bucket model, and remove #create hack

--- app/models/bucket-model.coffee
+++ app/models/bucket-model.coffee
@@ -6,6 +6,7 @@
     @singular: 'bucket'
     @plural: 'buckets'
     @indices: ['groupId', 'userId']
+    @serializableAttributes: ['description', 'name', 'target', 'groupId']

     relationships: ->
       @hasMany 'comments', sortBy: 'createdAt', sortDesc: true
@@ -20,12 +21,4 @@

     openForFunding: ->
       @remote.postMember(@id,'open_for_funding', {target: @target, fundingClosesAt: @fundingClosesAt})
-
-    # temp hack to allow post requests
-    create: ->
-      @remote.create
-        bucket:
-          description: @description
-          name: @name
-          target: @target
-          group_id: @groupId+


Expand the allowed subject upload list (#5532)

Remove the admin restriction for video and audio uploads.
Add text/* and application/json to the list of allowed MIME types.
Add .txt and .json to the list of allowed file extensions.

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -15,7 +15,7 @@

 NOOP = Function.prototype

-VALID_SUBJECT_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.mp3', '.m4a', '.mpeg']
+VALID_SUBJECT_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.mp3', '.m4a', '.mpeg', '.txt', '.json']
 INVALID_FILENAME_CHARS = ['/', '\\', ':', ',']
 MAX_FILE_SIZE = 1000 * 1024

@@ -206,7 +206,7 @@

You've reached your subject upload limit. Please  contact us to request changes to your allowance.

       else

-          <UploadDropTarget accept={"text/csv, text/tab-separated-values, image/*#{if isAdmin() then ', video/*, audio/*' else ''}"} multiple onSelect={@handleFileSelection}>
+          <UploadDropTarget accept={"text/csv, text/tab-separated-values, image/*, video/*, audio/*, text/*, application/json"} multiple onSelect={@handleFileSelection}>
             Drag-and-drop or click to upload manifests and subject images here (you must select the media files as well as the manifest)

             Manifests must be .csv or .tsv. The first row should define metadata headers. All other rows should include at least one reference to an image filename in the same directory as the manifest.

             Headers that begin with "#" or "//" denote private fields that will not be visible to classifiers in the main classification interface or in the Talk discussion tool.

@@ -271,7 +271,7 @@
       if file.type in ['text/csv', 'text/tab-separated-values']
         @_addManifest file
         gotManifest = true
-      else if file.type.indexOf('image/') is 0 or (isAdmin() and (file.type.indexOf('video/') is 0) or (file.type.indexOf('audio/') is 0))
+      else if file.type.indexOf('image/') is 0 or file.type.indexOf('video/') is 0 or file.type.indexOf('audio/') is 0 or file.type.indexOf('text/') is 0 or file.type.indexOf('application/json') is 0
         if file.size < MAX_FILE_SIZE or isAdmin()
           @state.files[file.name] = file
           gotFile = true


Add a section property to the page controller for section routes


status bar in ember version

status bar in ember version

--- app/scripts/application.coffee
+++ app/scripts/application.coffee
@@ -12,7 +12,8 @@

 Ember.TEMPLATES.application = Ember.Handlebars.compile '
   Clean Editor
-

+

+

ctrl

alt

shift


Dont pre filter problems

Dont pre filter problems

--- app/scripts/dcplotsctrl.coffee
+++ app/scripts/dcplotsctrl.coffee
@@ -244,7 +244,7 @@
         paymentTypes = createDim (d) -> d.payment_type
         paymentGroup = paymentTypes.group()

-        problems = createDim((d) -> d.hardware_problem).filter (d) -> d != "none"
+        problems = createDim((d) -> d.hardware_problem)
         problemsGroup = problems.group()

         amounts = createDim (d) -> d.amount_tsh


Remove dependency on simplifies segments, which is no longer needed in Paper 0.99

--- app/scripts/models/link.coffee
+++ app/scripts/models/link.coffee
@@ -1,10 +1,9 @@
 define [
   'spine'
   'models/moves_path'
-  'models/simplifies_segments'
   'models/link_shape'
   'spine.relation'
-], (Spine, MovesPath, SimplifiesSegments, LinkShape) ->
+], (Spine, MovesPath, LinkShape) ->

   class Link extends Spine.Model
     @configure "Link", "sourceId", "targetId", "segments", "shape"
@@ -14,19 +13,15 @@
     constructor: (attributes) ->
       super
       @k = v for k,v of attributes
-      @updateSegments(attributes.segments) if attributes

     select: ->
       @drawing().select(@)
-
-    updateSegments: (segments) =>
-      @segments = new SimplifiesSegments().for(segments)

     reconnectTo: (nodeId, offset) =>
       mover = new MovesPath(@toPath(), offset)
       mover.moveStart() if nodeId is @sourceId
       mover.moveEnd() if nodeId is @targetId
-      @updateSegments(mover.finalise())
+      @segments = mover.finalise()
       @save()

     paperId: =>
@@ -34,7 +29,7 @@

     toPath: =>
       s = LinkShape.find(@shape)
-      path = s.draw(@toSegments())
+      path = s.draw(@segments)
       path.name = @paperId()
       path

@@ -47,8 +42,4 @@
         sourceId: destroyed.sourceId
         targetId: destroyed.targetId
         segments: destroyed.segments
-        shape: destroyed.shape
-
-    toSegments: =>
-      for s in @segments
-        new paper.Segment(s.point, s.handleIn, s.handleOut)
+        shape: destroyed.shape


Use .then instead of deprecated .done on promises

--- lib/fuzzy-finder-view.coffee
+++ lib/fuzzy-finder-view.coffee
@@ -104,7 +104,7 @@

   openPath: (filePath, lineNumber, openOptions) ->
     if filePath
-      atom.workspace.open(filePath, openOptions).done => @moveToLine(lineNumber)
+      atom.workspace.open(filePath, openOptions).then => @moveToLine(lineNumber)

   moveToLine: (lineNumber=-1) ->
     return unless lineNumber >= 0
@@ -126,7 +126,7 @@
     else if not filePath
       return
     else if pane = atom.workspace.getActivePane()
-      atom.project.open(filePath).done (editor) =>
+      atom.project.open(filePath).then (editor) =>
         fn(pane, editor)
         @moveToLine(lineNumber)
     else


APNs push message 字符控制再265byte 已测试ok。清理log

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -45,30 +45,25 @@

             note_bytesize = JSON.stringify(note).replace(/[^\x00-\xff]/gi, "--").length
-            logger.verbose JSON.stringify(note).replace(/[^\x00-\xff]/gi, "--")
-            logger.verbose note_bytesize
+
             alert = note.alert
+
             oldAlert_bytesize = alert.replace(/[^\x00-\xff]/gi, "--").length
+
             if note_bytesize > 265
                 difference = note_bytesize - 265
                 alert_bytesize =  oldAlert_bytesize - difference - 3
-                logger.verbose oldAlert_bytesize
-                logger.verbose difference
-                logger.verbose alert
+
                 alertNew = ''
                 i = 0
                 while i <= alert.length
-
-                    logger.verbose alertNew.replace(/[^\x00-\xff]/gi, "--").length + alert.charAt(i).replace(/[^\x00-\xff]/gi, "--").length
-                    logger.verbose alert_bytesize
-                    logger.verbose alertNew.replace(/[^\x00-\xff]/gi, "--").length + alert.charAt(i).replace(/[^\x00-\xff]/gi, "--").length <= alert_bytesize

                     if alertNew.replace(/[^\x00-\xff]/gi, "--").length + alert.charAt(i).replace(/[^\x00-\xff]/gi, "--").length <= alert_bytesize
                         alertNew = alertNew + alert.charAt(i)
                     else
                         note.alert = alertNew + '...'
                         i = alert.length + 1
-
+
                     i++

             logger.verbose "APNS push msg: " + JSON.stringify(note)


More :art:

More :art:

Makes it more clear that body and tabStops are top-level variables

--- lib/snippet-expansion.coffee
+++ lib/snippet-expansion.coffee
@@ -10,11 +10,12 @@
     @selections = [@cursor.selection]

     startPosition = @cursor.selection.getBufferRange().start
+    {body, tabStops} = @snippet
     if @snippet.lineCount > 1 and indent = @editor.lineTextForBufferRow(startPosition.row).match(/^\s*/)[0]
       # Add proper leading indentation to the snippet
-      body = @snippet.body.replace(/\n/g, '\n' + indent)
+      body = body.replace(/\n/g, '\n' + indent)

-      tabStops = @snippet.tabStops.map (tabStop) ->
+      tabStops = tabStops.map (tabStop) ->
         tabStop.map (range) ->
           newRange = Range.fromObject(range, true) # Don't overwrite the existing range
           if newRange.start.row # a non-zero start row means that we're not on the initial line
@@ -22,8 +23,6 @@
             newRange.start.column += indent.length
             newRange.end.column += indent.length
           newRange
-    else
-      {body, tabStops} = @snippet

     @editor.transact =>
       newRange = @editor.transact =>


Use new API to destroy markers in snippets package

--- lib/snippet-expansion.coffee
+++ lib/snippet-expansion.coffee
@@ -64,7 +64,7 @@

   destroy: ->
     @unsubscribe()
-    @editSession.destroyMarker(marker) for marker in @tabStopMarkers
+    marker.destroy() for marker in @tabStopMarkers
     @editSession.snippetExpansion = null

   restore: (@editSession) ->


more debugging in the enable method

more debugging in the enable method

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -28,16 +28,16 @@
       minimum: 1

   activate: (state) ->
-    console.log 'autohide-tree-view: activating'
+    # console.log 'autohide-tree-view: activating'
     @enabled = state.enabled ? true
     @subs = new SubAtom()
     @subs.add atom.packages.onDidActivateInitialPackages =>
-      console.log 'autohide-tree-view: initialize after activate initial packages' unless @initialized
+      # console.log 'autohide-tree-view: initialize after activate initial packages' unless @initialized
       @initialize()
     if @enabled and atom.packages.isPackageActive 'tree-view'
-      console.log 'autohide-tree-view: initialize before activate initial packages' unless @initialized
+      # console.log 'autohide-tree-view: initialize before activate initial packages' unless @initialized
       @initialize()
-    console.log 'autohide-tree-view: done activating'
+    # console.log 'autohide-tree-view: done activating'

   deactivate: ->
     @initialized = false
@@ -102,15 +102,22 @@

   enable: (treeViewPkg) ->
     console.log 'autohide-tree-view: enable'
+    console.log 'autohide-tree-view: tree-view package enabled: ', atom.packages.isPackageActive 'tree-view'
     treeViewPkg ?= atom.packages.getActivePackage 'tree-view'
+    console.log 'autohide-tree-view: tree-view package found: ', treeViewPkg?
     return unless treeViewPkg?
     @enabled = true
     treeView = treeViewPkg.mainModule.createView()
+    console.log 'autohide-tree-view: tree-view model: ', treeView
     treeViewEl = atom.views.getView treeView
+    console.log 'autohide-tree-view: tree-view element: ', treeViewEl
     treeViewEl.classList.add 'autohide', 'autohide-hover-events'
     @applyHiddenWidth()
+    console.log 'autohide-tree-view: applied setting hiddenWidth'
     @applyAnimate()
+    console.log 'autohide-tree-view: applied setting animate'
     @hide true
+    console.log ''
     console.log 'autohide-tree-view: enabled'

   disable: ->


Fix destroy of status view bottom panel

Fix destroy of status view bottom panel

--- lib/views/status-view.coffee
+++ lib/views/status-view.coffee
@@ -10,12 +10,12 @@
         @div class: "#{params.type} message", params.message

     initialize: ->
-      @subscribe $(window), 'core:cancel', => @detach()
-      atom.workspace.addBottomPanel(item: this)
+      @subscribe $(window), 'core:cancel', => @destroy()
+      @panel ?= atom.workspace.addBottomPanel(item: this)
       setTimeout =>
-        @detach()
+        @destroy()
       , atom.config.get('git-plus.messageTimeout') * 1000

-    detach: ->
-      super
+    destroy: ->
+      @panel.destroy()
       @unsubscribe()