Upgrade validator co

Upgrade validator co

--- library/membrane/validate.coffee
+++ library/membrane/validate.coffee
@@ -59,9 +59,13 @@
     # which inherits `Primitives` as a collection of the validators.
     # If you want to add validators - create and set context class.
     @validationContext: (context) ->
-        invalid = "the #{context} is not a class"
         return @$vcontext if arguments.length is 0
-        assert _.isObject(context?.__super__), invalid
+        noContext = "the #{context} is not a class"
+        assert _.isObject(context?.__super__), noContext
+        noRun = "the #{context} has no valid run method"
+        noChain = "the #{context} has no valid chain method"
+        assert _.isFunction(context.prototype.run), noRun
+        assert _.isFunction(context.prototype.chain), noChain
         @$vcontext = context; return this

     # This method is a default implementation of the renderer that


Remove console.log

Remove console.log

--- app/assets/javascripts/georgia/checkbox.js.coffee
+++ app/assets/javascripts/georgia/checkbox.js.coffee
@@ -92,9 +92,7 @@
     $(el).data('state')

   setState: (el, state) ->
-    console.log 'State before: ' + $(el).data('state')
     $(el).data('state', state)
-    console.log 'State after: ' + $(el).data('state')

 $.fn.actsAsCheckboxable = () ->
   @each ->


Add back in currentUser

Add back in currentUser

--- app/assets/javascripts/issuable_context.js.coffee
+++ app/assets/javascripts/issuable_context.js.coffee
@@ -1,5 +1,5 @@
 class @IssuableContext
-  constructor: ->
+  constructor: (currentUser) ->
     @initParticipants()
     new UsersSelect(currentUser)
     $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})


OX-238 Fix mouse controls

OX-238 Fix mouse controls

--- app/assets/javascripts/oxalis/model/flycam.coffee
+++ app/assets/javascripts/oxalis/model/flycam.coffee
@@ -120,10 +120,10 @@
   move : (p) -> #move by whatever is stored in this vector
     @setGlobalPos([@globalPosition[0]+p[0], @globalPosition[1]+p[1], @globalPosition[2]+p[2]])

-  moveActivePlane : (p) ->
+  moveActivePlane : (p) -> # vector of voxels in BaseVoxels
     p = Dimensions.transDim(p, @activePlane)
     ind = Dimensions.getIndices(@activePlane)
-    zoomFactor = Math.pow(2, @integerZoomSteps[@activePlane])
+    zoomFactor = Math.pow(2, @zoomSteps[@activePlane])
     scaleFactor = @model.scaleInfo.baseVoxelFactors
     delta = [p[0]*zoomFactor*scaleFactor[0], p[1]*zoomFactor*scaleFactor[1], p[2]*zoomFactor*scaleFactor[2]]
     # change direction of the value connected to space, based on the last direction


added null check for child events in processAppointmentData

fixes #CNVS-3387. The CommonEventFactory was returning null
for some child events because it couldn't find the proper
context for them. This might be a legitimate filter, although
the api probably shouldn't be returning them. Either way, it
makes sense to put a null check on the results of the factory
since we do this elsewhere as well.

Test Plan:
 - Check for scheduler regressions

Change-Id: Idca8942ac5e138107248cda9c4dd04452823239e
Reviewed-on: https://gerrit.instructure.com/17290
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Zach Pendleton <514dbb4d54d4f29e454990df7d075678f43f92aa@instructure.com>
QA-Review: Marc LeGendre <d33c80bc45d65303e33ca83108a9952b745af9ef@instructure.com>

--- app/coffeescripts/calendar/EventDataSource.coffee
+++ app/coffeescripts/calendar/EventDataSource.coffee
@@ -184,7 +184,8 @@
               for childEventData in eventData.child_events
                 childEvent = commonEventFactory(childEventData, @contexts)
                 @addEventToCache event
-                event.childEvents.push childEvent
+                if childEvent
+                  event.childEvents.push childEvent

     getEventsForAppointmentGroup: (group, cb) =>
       if @inFlightRequest


Fix bossfight

Fix bossfight

--- app/scripts/lazerscripts/stage1/stage1boss.coffee
+++ app/scripts/lazerscripts/stage1/stage1boss.coffee
@@ -144,7 +144,7 @@

   fase2: ->
     # start at 345000
-    @bindSequence 'Hit', @fase3, => @entity.health < 320000
+    @bindSequence 'Hit', @fase3, => @entity.health < 330000

     @sequence(
       @setSpeed 50
@@ -170,12 +170,12 @@
     )

   fase3: ->
-    @bindSequence 'Hit', @fase4, => @entity.health < 275000
+    @bindSequence 'Hit', @fase4, => @entity.health < 285000

     @sequence(
       @setSpeed 50
       @bombRaid(yes)
-      @attackCycle3(7)
+      @attackCycle3(6)
     )

   fase4: ->
@@ -275,7 +275,7 @@
     )

   execute: ->
-    @bindSequence 'Hit', @leaveScreen, => @entity.health < 230000
+    @bindSequence 'Hit', @leaveScreen, => @entity.health < 255000
     @inventoryAdd 'item', 'xp', ->
       Crafty.e('PowerUp').powerUp(contains: 'xp', marking: 'X')


yep, still need to sync every tick

yep, still need to sync every tick

--- src/universe.coffee
+++ src/universe.coffee
@@ -32,7 +32,7 @@
     @drawPowerups()

     # sync self every tick?
-    @syncSelf() if @tick_count % constants.syncTimer == 0
+    @syncSelf() #if @tick_count % constants.syncTimer == 0

     setTimeout (=> @gameTick()), 40


Remove limit for username and profile name

--- src/userForm.coffee
+++ src/userForm.coffee
@@ -13,13 +13,11 @@
   schema = new SimpleSchema
     username:
       type: String
-      max: 20
     password:
       type: String
       optional: true
     'profile.name':
       type: String
-      max: 20
     email:
       type: String
       regEx: SimpleSchema.RegEx.Email


+ split 4 dataSets

+ split 4 dataSets


Don't barf if manifest isn't ready yet

Don't barf if manifest isn't ready yet


Comment.

Comment.

--- library/Keys.coffee
+++ library/Keys.coffee
@@ -65,8 +65,8 @@
   scrypt(secret, salt, logN, r, dkLen, interruptStep, whenKeysAreReady, encoding)

-# miniLock only accepts secret phrases that are at least 32 characters long and
-# have at least 100 bits of entropy.
+# An acceptable secret phrases is at least 32 characters long and
+# has at least 100 bits of entropy.
 exports.secretPhraseIsAcceptable = (secretPhrase) ->
   secretPhrase?.length >= 32 and zxcvbn(secretPhrase).entropy >= 100


passing tests

passing tests

--- client/apps/edit/components/section_text/test/index.coffee
+++ client/apps/edit/components/section_text/test/index.coffee
@@ -13,7 +13,9 @@
   beforeEach (done) ->
     benv.setup =>
       benv.expose $: require 'jquery'
-      SectionText = benv.require resolve(__dirname, '../index')
+      SectionText = benv.requireWithJadeify(
+        resolve(__dirname, '../index'), ['icons']
+      )
       SectionText.__set__ 'Scribe', @Scribe = sinon.stub()
       SectionText.__set__ 'scribePluginToolbar', @scribePluginToolbar = sinon.stub()
       @component = React.render SectionText(


vm controller revert

vm controller revert

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -55,14 +55,14 @@
   reinitialize:(vm, callback)->
     @_runWrapper 'vm.reinitialize', vm, callback

-  _deleteVM: (vm, cb)->
+  remove: do->
+
+    deleteVM = (vm, cb)->
       KD.remote.api.JVM.removeByHostname vm, (err)->
         return cb err  if err
         KD.getSingleton("finderController").unmountVm vm
         KD.getSingleton("vmController").emit 'VMListChanged'
         cb null
-
-  remove: do->

     (vm, callback=noop)->
       KD.remote.api.JVM.fetchVmInfo vm, (err, vmInfo)=>


Removed unnecessary code.

Removed unnecessary code.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -111,7 +111,7 @@

     # Take this to a better place, possibly to payment controller.
-    makePayment = (type, hostname, plan, callback)->
+    makePayment = (type, plan, callback)->
       planCode = plan.code
       if type is 'shared'
         group = KD.singletons.groupsController.getCurrentGroup()
@@ -218,8 +218,7 @@
                 form                = modal.modalTabs.forms["Create VM"]
                 {personal, shared}  = form.buttons

-                # Gökmen buraya!
-                makePayment formData.type, "HOSTNAME", @paymentPlans[formData.host], ->
+                makePayment formData.type, @paymentPlans[formData.host], ->
                   modal.destroy()
               buttons               :
                 personal            :


do not display 0% while loading

do not display 0% while loading

--- src/loader/loader.coffee
+++ src/loader/loader.coffee
@@ -13,6 +13,7 @@

     @visible = @alive = @exists = true

+    # FIXME: do not hardcode these ...
     @w = 256
     @h = 24

@@ -32,7 +33,7 @@
         if ++@loaded >= @total
           @onload()

-        console.log("Loaded: #{((@pct * 100) | 0)}%")
+        console.log("Loaded: #{Math.ceil(@pct * 100)}%") if @pct > 0

       console.log("Registered loader #{k} for the `#{_loader.extension}` extension.")
       @_loaders[_loader.extension] = _loader


refactor(): factor out a _polyglotReset() method

--- src/madlib-locale.coffee
+++ src/madlib-locale.coffee
@@ -118,11 +118,8 @@
             #
             if @cache[ locale ]?

-                @locale = @cache[ locale ]
-                @polyglot.locale(  @locale.name )
-                @polyglot.replace( @locale.phrases )
-
-                return Q( @locale )
+                return Q( @_polyglotReset( @locale = @cache[ locale ] ) )
+

             # Load the new locale phrases
             #
@@ -144,18 +141,9 @@

             return loaded.then( ( data ) =>

-                # Set polyglot locale and phrases on success
-                #
-                @locale = data.response
-
-                @polyglot.locale(  @locale.name )
-                @polyglot.replace( @locale.phrases )
-
-                # Add the default locale to the cache
-                #
-                @cache[ @locale.name ] = @locale
-
-                return @locale
+                locale  = data.response
+
+                return @_polyglotReset( @locale = @cache[ locale.name ] = locale )
             )

@@ -190,6 +178,27 @@

             return accounting.formatNumber( number, precision, thousand, decimal )

+
+        ###*
+        #   Reset our `@polyglot` to a new locale definition.
+        #
+        #   @method     _polyglotReset
+        #   @protected
+        #
+        #   @param      {Object}        locale                          A locale definition
+        ###
+
+        _polyglotReset: ( locale ) ->
+
+            { polyglot }    = @
+
+            polyglot.locale(  locale.name       )
+            polyglot.replace( locale.phrases    )
+
+            return locale
+
+
+
     # We only need one translator
     #
     localeManager = new LocaleManager()


(fix) Dont hide the active buffer.

(fix) Dont hide the active buffer.

--- src/more_markdown.coffee
+++ src/more_markdown.coffee
@@ -29,14 +29,14 @@
   active.style.left = "-1000%";
   active.style.top = "-1000%";
   active.style.zIndex = -1000000;
-  active.style.opacity = "1";
+  active.style.opacity = "0";
   buffer.setAttribute "activeBuffer", ""
   buffer.removeAttribute "invisibleBuffer"
   buffer.style.position = ""
   buffer.style.top = "";
   buffer.style.left = "";
   buffer.style.zIndex = 0;
-  buffer.style.opacity = "0";
+  buffer.style.opacity = "1";

   # creates an advanced markdown processor for that specific dom element
   # does some magic to remove flickering etc.


add cursors to dual demo

add cursors to dual demo

--- demo/scripts/dual.coffee
+++ demo/scripts/dual.coffee
@@ -1,17 +1,24 @@
 listenEditor = (source, target) ->
   source.on(Scribe.Editor.events.TEXT_CHANGE, (delta) ->
-    console.info 'text change', delta
+    console.info source.id, 'text change', delta
     target.applyDelta(delta)
     sourceDelta = source.doc.toDelta()
     targetDelta = target.doc.toDelta()
     console.assert(sourceDelta.isEqual(targetDelta), "Editor diversion!", source, target)
+  ).on(Scribe.Editor.events.SELECTION_CHANGE, (range) ->
+    console.info source.id, 'selection change', range.start.index, range.end.index
+    target.cursorManager.setCursor(source.id, range.end.index, source.id, 'blue')
   )

 editors = _.map([1, 2], (num) ->
   editor = new Scribe.Editor('editor-container' + num)
   toolbar = new Scribe.Toolbar("formatting-container#{num}", editor)
+  editor.cursorManager = new Scribe.MultiCursor(editor)
+  editor.attributionManager = new Scribe.Attribution(editor, editor.id, 'blue')
   return editor
 )

 listenEditor(editors[0], editors[1])
 listenEditor(editors[1], editors[0])
+editors[0].attributionManager.addAuthor(editors[1].id, 'blue')
+editors[1].attributionManager.addAuthor(editors[0].id, 'blue')


Fixed the filter of the file size to show 0 bytes when the file does not exist

--- app/coffee/filters.coffee
+++ app/coffee/filters.coffee
@@ -39,12 +39,15 @@
     return (input, precision) ->
         if isNaN(parseFloat(input)) or !isFinite(input)
             return '-'
+
+        if input == 0
+            return '0 bytes'
+
         if precision == 'undefined'
             precision = 1

         units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
         number = Math.floor(Math.log(input) / Math.log(1024))
-
         return (input / Math.pow(1024, Math.floor(number))).toFixed(precision) +  ' ' +
                units[number]


fixed few things

fixed few things

fixed some wordings and sentences

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -247,7 +247,7 @@
     victory_saving_progress: "保存进度"
     victory_go_home: "返回主页"
     victory_review: "给我们反馈！"
-#    victory_review_placeholder: "How was the level?"
+    victory_review_placeholder: "关卡如何?"
     victory_hour_of_code_done: "你完成了吗?"
     victory_hour_of_code_done_yes: "是的, 完成了!"
     victory_experience_gained: "获得经验"
@@ -294,7 +294,7 @@
     tip_scrub_shortcut: "用 Ctrl+[ 和 Ctrl+] 来倒退和快进。" # {change}
     tip_guide_exists: "点击页面上方的指南, 可以获得更多有用信息。"
     tip_open_source: "「CodeCombat」是100%开源的！"
-#    tip_tell_friends: "Enjoying CodeCombat? Tell your friends about us!"
+    tip_tell_friends: "喜欢Codecombat？那就赶快把它安利给朋友！"
     tip_beta_launch: "CodeCombat开始于2013的10月份。"
     tip_think_solution: "思考如何解决, 而不是思考问题。"
     tip_theory_practice: "在理论上，理论和实践之间是没有区别的。但在实践上，它们是有区别的。 - Yogi Berra"
@@ -336,7 +336,7 @@
     tip_hate_computers: "那些认为他们讨厌电脑的人，其实他们讨厌的是垃圾程序编写员。- Larry Niven"
     tip_open_source_contribute: "你可以帮助「CodeCombat」提高！"
     tip_recurse: "迭代为人，递归为神 - L. Peter Deutsch"
-    tip_free_your_mind: "丢掉一切私心杂念,丢掉害怕、疑问和拒信,解放你的思想。 - Morpheus"
+    tip_free_your_mind: "丢掉一切私心杂念，丢掉害怕、疑问和拒信，解放你的思想。 - Morpheus（黑客帝国）"
     tip_strong_opponents: "即使是最强大的对手也是有弱点的. - Itachi Uchiha"
 #    tip_paper_and_pen: "Before you start coding, you can always plan with a sheet of paper and a pen."


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.coffee
+++ app/locale/zh-HANS.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}


fix: catch non retryable error properly

fix: catch non retryable error properly

--- src/processor.coffee
+++ src/processor.coffee
@@ -21,7 +21,9 @@

       $promise
       .tap => @_emitEvent "successful", { context, id, notification }
-      .catch NonRetryable, (error) => @_emitEvent "unsuccessful_non_retryable", { context, id, notification, error }
+      .catch (error) =>
+        throw error unless error instanceof NonRetryable
+        @_emitEvent "unsuccessful_non_retryable", { context, id, notification, error }
       .tapCatch (error) => @_emitEvent "unsuccessful", { context, id, notification, error }
       .finally => @_emitEvent "finished", { context, id, notification }
       .asCallback context.done


testing Object#isArray

testing Object#isArray

--- src/quad_tree.coffee
+++ src/quad_tree.coffee
@@ -21,7 +21,7 @@
         clear: -> root.clear()

         insert: (obj) ->
-          if Object.prototype.toString.call(obj) == '[object Array]'
+          if obj.isArray()
             obj.each (item) ->
               root.insert(item)
           else


focus on first form field on create

focus on first form field on create


Both focus and select on spreadsheet activation

Date fields apparently don't take select

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -825,7 +825,7 @@
           tooltips.clear()
           $timeout ->
             input = e.find('[name=edit]')
-            input.filter('input,textarea').select()
+            input.filter('input,textarea').focus().select()
             input.filter('select').one('change', $scope.$lift(editScope.unedit))
             return
           return


better placeholder text for chat box

better placeholder text for chat box

--- app/components/TextChat.react.coffee
+++ app/components/TextChat.react.coffee
@@ -68,7 +68,7 @@
         </div>
         <div className="new-message">
           <form onSubmit={@_onSubmit}>
-            <textarea placeholder="Chat..." onKeyDown={@_onKeyDown} onChange={@_onTextChange} value={@state.text}/>
+            <textarea placeholder="Type a message ..." onKeyDown={@_onKeyDown} onChange={@_onTextChange} value={@state.text}/>
           </form>
         </div>
       </div>


fixed castling, resolves #2

fixed castling, resolves #2

--- app/scripts/chess/pieces/king.coffee
+++ app/scripts/chess/pieces/king.coffee
@@ -8,17 +8,25 @@

 	getMoves: (x,y,board) ->
 		moves = []
+
 		# castling
 		unless @hasMoved
 			# left side
 			if board.clearStraight(x,y,0,y)
 				if not board.get(0,y).hasMoved and board.get(0,y) instanceof Rook
-					moves.push [2,y, "castle"]
+					# cannot castle through a check
+					unless board.inCheck(1, y, @isWhite) ||
+					board.inCheck(2, y, @isWhite) ||
+					board.inCheck(3, y, @isWhite)
+						moves.push [2,y, "castle"]

 			# right side
 			if board.clearStraight(x,y,7,y)
 				if not board.get(0,y).hasMoved and board.get(0,y) instanceof Rook
-					moves.push [6,y, "castle"]
+					# cannot castle through a check
+					unless board.inCheck(5, y, @isWhite) ||
+					board.inCheck(6, y, @isWhite)
+						moves.push [6,y, "castle"]

 		# regular moves
 		for j in [0..7]


added support for url configuration in caboose-model

--- caboose-model/lib/connection.coffee
+++ caboose-model/lib/connection.coffee
@@ -14,13 +14,10 @@
       [options.user, options.password] = uri.auth.split ':'
     options

-  open: (conn_string, callback) ->
+  open: (options, callback) ->
     return callback null, @db if @db?

-    if typeof conn_string is 'string'
-      options = @_parse_options conn_string
-    else
-      options = conn_string
+    options = @_parse_options(options.url) if options.url?

     if not @db
       try


not sure

not sure

--- app/server/methods/generators.coffee
+++ app/server/methods/generators.coffee
@@ -136,7 +136,7 @@
         toPdf = child.spawn "wkhtmltopdf", [htmlFile, pdfFile]
         toPdf.stdout.setEncoding('utf8');
         toPdf.stdout.on 'data', (data) ->
-          console.log("wkhtmltopdf:", data)
+          #console.log("wkhtmltopdf:", data)

         toPdf.stderr.setEncoding('utf8');
         toPdfStderr = ""
@@ -205,7 +205,7 @@
         toPdf = child.spawn "wkhtmltopdf", [htmlFile, pdfFile]
         toPdf.stdout.setEncoding('utf8');
         toPdf.stdout.on 'data', (data) ->
-          console.log("wkhtmltopdf:", data)
+          #console.log("wkhtmltopdf:", data)

         toPdf.stderr.setEncoding('utf8');
         toPdfStderr = ""


Add status reset on node popup close.

Add status reset on node popup close.

--- app/assets/javascripts/controllers/node.js.coffee
+++ app/assets/javascripts/controllers/node.js.coffee
@@ -26,6 +26,19 @@
     '/nodes/' + @get('osm_id') + '/update_wheelchair.js'
   ).property('osm_id')

+  popupClosed: (()->
+    # Reset if popup was closed and new wheelchair status was not saved
+    if @get('isPopping')
+      return
+
+    @resetStatus()
+  ).observes('isPopping')
+
+  resetStatus: ()->
+    if @get('oldWheelchair')?
+      @set('wheelchair', @get('oldWheelchair'))
+      @get('oldWheelchair', null)
+
   actions:
     setWheelchair: (wheelchair)->
       unless @get('isPosting')
@@ -50,12 +63,12 @@
         dataType: 'json'

       promise.done (response)->
+        self.set('oldWheelchair', null)
         self.get('controllers.flash-messages').pushMessage('notice', response.message)

       promise.fail ()->
-        self.set('wheelchair', self.get('oldWheelchair'))
+        @resetStatus()
         self.get('controllers.flash-messages').pushMessage('error', response.message)

       promise.always ()->
-        self.set('oldWheelchair', null)
         self.set('isPosting', false)


revert back to website_path

revert back to website_path

--- app/assets/javascripts/pages/installations.coffee
+++ app/assets/javascripts/pages/installations.coffee
@@ -1,7 +1,7 @@
 PubSub.subscribe('assigned.website', (ev, website)->
   switch gon.action
     when "show"
-      $.getJSON Routes.api_v1_websites_url() + '/' + website.id, (data) ->
+      $.getJSON Routes.api_v1_website_path(website.id), (data) ->
         $('#current-website').render data
         console.log 'website loaded'
 )


The server sends JSON without a root

The server sends JSON without a root

--- app/assets/javascripts/rglossa/react/store.coffee
+++ app/assets/javascripts/rglossa/react/store.coffee
@@ -9,7 +9,7 @@
       do (model) =>
         $.getJSON(model)
           .done( (res) =>
-            @models[model] = res[model])
+            @models[model] = res)
           .fail -> alert('Error fetching data from the server. Please reload the page.')
     $.when.apply(null, promises).done(=> @storeChangedHandler(@))


Set dropZone to uploadForm by default.

Set dropZone to uploadForm by default.

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -35,6 +35,7 @@

   setUploadForm = ->
     $uploadForm.fileupload
+      dropZone: $uploadForm

       add: (e, data) ->
         file = data.files[0]


improvements

improvements

--- app/assets/javascripts/shared/bootstrap.js.coffee
+++ app/assets/javascripts/shared/bootstrap.js.coffee
@@ -6,8 +6,6 @@
   $("a[rel~=popover], .has-popover").popover()
   $("a[rel~=tooltip], .has-tooltip").tooltip()

-  recalc_padding()
-
   $(window).on 'resize', recalc_padding

-
+$(window).load(-> recalc_padding());


menu dziala

menu dziala

--- app/assets/javascripts/store/image_menu.js.coffee
+++ app/assets/javascripts/store/image_menu.js.coffee
@@ -24,7 +24,7 @@
     #$(@).find("img.color").first().stop().animate({"opacity": "0"}, "slow")

   $(".menu_container").mouseleave (e) ->
-    $("ul.subimage_menu").stop(true, true).delay(1000).fadeOut(2000)
+    $("ul.subimage_menu").stop(true, true).delay(100).fadeOut(2000)
     parent_id = $("ul.subimage_menu").data("parent")
     $("#"+parent_id).find("img.grey").first().stop().animate({"opacity": "1"}, "fast")
     $("#"+parent_id).find("img.color").first().stop().animate({"opacity": "0"}, "slow")


remove whitespaces

remove whitespaces

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -201,7 +201,7 @@

     if settings.validators
       settings.validators = settings.validators.slice()
-
+
     settings


fixed spacing bug in organizations so now the commands appear with hubot help

--- src/organizations.coffee
+++ src/organizations.coffee
@@ -1,4 +1,4 @@
- # Description:
+# Description:
 #   Allow Hubot to manage your github organization members and teams
 #
 # Dependencies:


Prevent badge iframe from being hidden

Prevent badge iframe from being hidden

--- src/plugins/badge.coffee
+++ src/plugins/badge.coffee
@@ -248,10 +248,16 @@
   }

   #sa-badge-modal #sa-badge-modal-inner #sa-badge-modal-iframe {
-    display: block;
-
     position: relative;
     z-index: 3;
+
+    display: block;
+    visibility: visible;
+
+    width: 100%;
+    height: 100%;
+
+    opacity: 1;
   }

   #sa-badge-modal #sa-badge-modal-inner #sa-badge-modal-close-button {


Fixes Gallery Insights errors

Fixes Gallery Insights errors

--- apps/articles/routes.coffee
+++ apps/articles/routes.coffee
@@ -133,8 +133,7 @@
 subscribedToEditorial = (email, callback) ->
   sailthru.apiGet 'user', { id: email }, (err, response) ->
     return callback err, false if err
-    subscribed = response.vars?.receive_editorial_email
-    callback null, subscribed
+    callback null, response.vars?.receive_editorial_email

 @editorialForm = (req, res, next) ->
   sailthru.apiPost 'user',


hide toolpad when tyoing

hide toolpad when tyoing

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -361,6 +361,7 @@
 	bindEditorEvents: ->
 		$('#editor').on 'keydown', (e)=>
 			@hideTooltip()
+			@hideToolpad()
 			@checkNew() if @status.new
 			@handleKeyDown(e)
 			@update()


better tool panels presentation

better tool panels presentation

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -11,6 +11,8 @@
 		new: true
 		connecting: false
 		changed: false
+		showToolpad: false
+		showTooltip: false
 	constructor: (options={})->
 		@id = options.id
 		@status.new = !@id
@@ -122,9 +124,11 @@
 			, durationInMilliseconds)()

 	showTooltip: ->
-		$('#tooltip').removeClass 'hidden'
+		$('#tooltip').removeClass 'hidden' unless @status.showToolpad
+		@status.showTooltip = true
 	hideTooltip: ->
 		$('#tooltip').addClass 'hidden'
+		@status.showTooltip = false
 	setTooltipTop: (top)->
 		$('#tooltip').css 'top', top
 	updateTooltipStatus: (jqel)->
@@ -143,11 +147,14 @@
 		$('#toolpad').css 'top', top - 35
 		$('#toolpad').css 'left', left - 90
 		@update()
+		@hideTooltip()
+		@status.showToolpad = true
 	hideToolpad: ->
 		$('#toolpad').addClass 'hidden'
 		$('#toolpad li:first-child').removeClass 'toggled'
-		@.lastLinkElement = null
-		@.linkRange = null
+		@lastLinkElement = null
+		@linkRange = null
+		@status.showToolpad = false
 	cancelLinkInput: ->
 		$('#toolpad li:first-child').removeClass 'toggled'


refactor toolpad

refactor toolpad

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -254,6 +254,7 @@
 		$('body').delegate '.content', 'mouseleave', (e)=>
 			@hideTooltip()
 	delegateEditorToolpadEvents: ()->
+		self = @
 		$('body').delegate '#editor', 'mouseup', (e)=>
 			setTimeout =>
 				@detectToShowToolpad()
@@ -266,10 +267,7 @@
 		# apply link URL
 		$('#toolpad').delegate 'li:first-child', 'click', (e)->
 			self.openLinkInput()
-		# .delegate 'li:first-child', 'mouseenter', (e)->
-		# 	self.linkRange = self.selection().getRangeAt 0
 		.delegate 'input', 'keydown', (e)->
-				# self.lastLinkElement.setAttribute 'href', $(@).val()
 			if e.keyCode == 13
 				if self.linkRange
 					self.applyOrCancelUrl self.linkRange, $(@).val()
@@ -433,4 +431,5 @@

 		# dynamic events
 		@delegateEditorEvents()
-		@delegateEditorTooltipEvents()+		@delegateEditorTooltipEvents()
+		@delegateEditorToolpadEvents()


Implement relationship delete rule

Implement relationship delete rule

--- app/src/base/model.coffee
+++ app/src/base/model.coffee
@@ -74,15 +74,33 @@

   save: () ->
     if @isInserted() and @hasChange() and @onUpdate() isnt false
-      @_commitPendingRelationship()
+      @_commitPendingRelationships()
       @_collection.update this
     else if @onInsert() isnt false
       @_collection.insert this
-      @_commitPendingRelationship()
+      @_commitPendingRelationships()
       @_needsUpdate = no

   delete: () ->
     if not @_deleted and @onDelete() isnt false
+      for relationship in @getRelationships()
+        switch relationship.onDelete
+          when 'destroy'
+            switch relationship.type
+              when 'many_one'
+                item.delete() for item in @get(relationship.name)
+              when 'one_one'
+                @get(relationship.name).delete()
+              when 'one_many'
+                @get(relationship.name).delete()
+              when 'many_many' then throw 'many:many relastionships are not implemented yet'
+          when 'nullify'
+            switch relationship.type
+              when 'many_one'
+                item.set(relationship.inverse, null) for item in @get(relationship.name)
+              when 'one_one'
+                @get(relationship.name).set(relationship.inverse, null)
+              when 'many_many' then throw 'many:many relastionships are not implemented yet'
       @_deleted = true
       @_collection.remove @_object

@@ -114,21 +132,57 @@
   getRelationships: () -> @constructor._relationships

   _getModelValue: (relationship, value) ->
-
-
-  _commitPendingRelationship: () ->
+    ValueClass = window[relationship.Class]
+    unless _(value).isKindOf ValueClass
+      value = new ValueClass(@_context, value)
+      value.save()
+    value
+
+  _commitAddPendingRelationship: (pending, relationship) ->
+    switch relationship.type
+      when 'many_one'
+        for v in pending.value
+          value = @_getModelValue(relationship, v)
+          value.set("#{relationship.inverse}_id", @getId())
+          value.save()
+      when 'one_many'
+        value = @_getModelValue(relationship, pending.value)
+        @_object["#{relationship.name}_id"] = value.getId()
+        @_context.update this
+      when 'one_one'
+        value = @_getModelValue(relationship, pending.value)
+        @_object["#{relationship.name}_id"] = value.getId()
+        value.set("#{relationship.inverse}_id", @getId())
+        value.save()
+        @_context.update this
+      when 'many_many' then throw 'many:many relationships are not implemented yet'
+
+  _commitRemovePendingRelationship: (pending, relationship) ->
+    switch relationship.type
+      when 'many_one'
+        for v in pending.value
+          value = @_getModelValue(relationship, v)
+          value.set("#{relationship.inverse}_id", null)
+          value.save()
+      when 'one_many'
+        @_object["#{relationship.name}_id"] = null
+        @_context.update this
+      when 'one_one'
+        value = @_getModelValue(relationship, pending.value)
+        @_object["#{relationship.name}_id"] = value.getId()
+        value.set("#{relationship.inverse}_id", null)
+        value.save()
+        @_context.update this
+      when 'many_many' then throw 'many:many relationships are not implemented yet'
+
+  _commitPendingRelationships: () ->
     for relationshipName, pending of @_pendingRelationships
       relationship = @getRelationships()[relationshipName]
       continue unless relationship?
-      switch relationship.type
-        when 'many_one'
-          for v in pending.value
-            value = @_getModelValue(relationship, v)
-        when 'one_many'
-          value = @_getModelValue(relationship, pending.value)
-        when 'one_one'
-          value = @_getModelValue(relationship, pending.value)
-        when 'many_many' then throw 'many:many relationships are not implemented yet'
+      if pending.add is true
+        @_commitAddPendingRelationship(pending, relationship)
+      else
+        @_commitRemovePendingRelationship(pending, relationship)
     @_pendingRelationships = null

   @create: (base, context = ledger.db.contexts.main) -> new @ context, base


levels and default game states

levels and default game states

--- src/title_screen.coffee
+++ src/title_screen.coffee
@@ -7,6 +7,6 @@
   self.bind "overlay", (canvas) ->
     self.centerText canvas, App.name

-    s
+    self.center

   return self


Render earlier

Render earlier

--- core/app/backbone/controllers/client_controller.coffee
+++ core/app/backbone/controllers/client_controller.coffee
@@ -31,9 +31,10 @@

     fact.on 'destroy', => @annotatedSiteEnvoy 'deleteFactlink', fact.id

+    FactlinkApp.discussionSidebarContainer.slideIn new ReactView
+      component: ReactDiscussion
+        model: fact
+
     fact.fetch
       success: =>
         @annotatedSiteEnvoy 'openModalOverlay'
-        FactlinkApp.discussionSidebarContainer.slideIn new ReactView
-          component: ReactDiscussion
-            model: fact


Encode selected/not selected state more explicitly

--- core/app/backbone/views/facts/react_fact_search.coffee
+++ core/app/backbone/views/facts/react_fact_search.coffee
@@ -24,7 +24,7 @@
   changeOptions: 'add remove reset sort sync'

   getInitialState: ->
-    selectedModelIndex: null
+    hasModelSelected: false

   _childView: (fact, index) ->
     ReactFactSearchResult
@@ -33,12 +33,14 @@
       selected: index == @state.selectedModelIndex
       ref: index
       key: fact.id
-      onMouseEnter: => @setState selectedModelIndex: index
-      onMouseLeave: => @setState selectedModelIndex: null
-      onClick: => @setState selectedModelIndex: index; @props.onSelect?()
+      onMouseEnter: => @setState hasModelSelected: true, selectedModelIndex: index
+      onMouseLeave: => @setState hasModelSelected: false
+      onClick: =>
+        @setState hasModelSelected: true, selectedModelIndex: index
+        @props.onSelect?()

   selectedModel: ->
-    return unless @state.selectedModelIndex?
+    return unless @state.hasModelSelected

     @model().at(@state.selectedModelIndex)

@@ -59,14 +61,14 @@

   _select: (index) ->
     index = @_fixIndexModulo(index)
-    @setState selectedModelIndex: index
+    @setState hasModelSelected: true, selectedModelIndex: index
     @refs[index].scrollIntoView()

   moveSelectionUp: ->
-    @_select if @state.selectedModelIndex? then @state.selectedModelIndex-1 else -1
+    @_select if @state.hasModelSelected then @state.selectedModelIndex-1 else -1

   moveSelectionDown: ->
-    @_select if @state.selectedModelIndex? then @state.selectedModelIndex+1 else 0
+    @_select if @state.hasModelSelected then @state.selectedModelIndex+1 else 0

 window.ReactFactSearch = React.createBackboneClass
   displayName: 'ReactFactSearch'


Fix Pencil import on case sensitive filesystems

--- src/tools/Eraser.coffee
+++ src/tools/Eraser.coffee
@@ -1,4 +1,4 @@
-Pencil = require './pencil'
+Pencil = require './Pencil'
 {createShape} = require '../core/shapes'


Port wage schedule calculation over to server-side

--- src/routes/survey.coffee
+++ src/routes/survey.coffee
@@ -85,12 +85,11 @@
   debug "Schedule: #{schedule}"
   debug "Schedule: #{wageSchedules[schedule]}"

-  for wageInterval in [0..wageSchedules[schedule].length] by 1
-    debug "Wage Interval #{wageInterval}"
-    current = wageSchedules[schedule][wageInterval]
+  for current, i in wageSchedules[schedule]
+    debug "Current: #{JSON.stringify current}"

     if schedule == "D"
-      compensation = wageSchedules["C"][wageInterval]
+      compensation = wageSchedules["C"][i]

       if compensation?
         current.compensation = compensation.wage


Focus to input box when switching back from preview

--- resources/assets/coffee/_classes/forum-post-preview.coffee
+++ resources/assets/coffee/_classes/forum-post-preview.coffee
@@ -68,5 +68,8 @@

   hidePreview: (e) =>
-    $(e.target).parents('.js-forum-post-preview--form').attr('data-state', 'write')
+    $form = $(e.target).parents('.js-forum-post-preview--form')
+    $form.attr('data-state', 'write')
     osu.pageChange() # sync height of reply box
+
+    $form.find('.js-forum-post-preview--body').focus()


use props directly

use props directly

--- resources/assets/coffee/react/beatmaps/search-panel.coffee
+++ resources/assets/coffee/react/beatmaps/search-panel.coffee
@@ -37,9 +37,6 @@
     @debouncedSubmit = _.debounce @submit, 500
     @breadcrumbsElement = window.stickyHeader.breadcrumbsElement()
     @contentElement = window.stickyHeader.contentElement()
-
-    @state =
-      query: @props.filters.query

   componentDidMount: =>
@@ -51,10 +48,6 @@

     @breadcrumbsElement?.appendChild @breadcrumbsPortal
     @contentElement?.appendChild @contentPortal
-
-
-  componentDidUpdate: (prevProps, prevState) =>
-    @debouncedSubmit @state.query if prevState.query != @state.query

   componentWillUnmount: =>
@@ -108,7 +101,7 @@
           name: 'search'
           onChange: @onChange
           placeholder: osu.trans('beatmaps.listing.search.prompt')
-          value: @state.query
+          value: @props.filters.query
         div className: 'beatmapsets-search__icon',
           i className: 'fas fa-search'

@@ -126,8 +119,7 @@

   onChange: (event) =>
-    @setState
-      query: event.target.value
+    @debouncedSubmit event.target.value

   renderFilter: ({ multiselect = false, name, options, showTitle = true }) =>
@@ -180,7 +172,7 @@
           name: 'search'
           onChange: @onChange
           placeholder: osu.trans('beatmaps.listing.search.prompt')
-          value: @state.query
+          value: @props.filters.query
         div className: 'beatmapsets-search__icon',
           i className: 'fas fa-search'


Set max width property to tab handle elements.

--- client/Framework/components/tabs/KDTabView.coffee
+++ client/Framework/components/tabs/KDTabView.coffee
@@ -59,6 +59,9 @@
       @appendPane paneInstance
       @showPane paneInstance
       @emit "PaneAdded", paneInstance
+
+      newTabHandle.$().css maxWidth: @getOptions().maxHandleWidth
+
       return paneInstance
     else
       warn "You can't add #{paneInstance.constructor.name if paneInstance?.constructor?.name?} as a pane, use KDTabPaneView instead."


reactivity:  code cleanup

reactivity:  code cleanup

--- client/activity/lib/flux/chatinput/getters.coffee
+++ client/activity/lib/flux/chatinput/getters.coffee
@@ -148,7 +148,7 @@
       category  : item.get('category')
       iconEmoji : item.get('emojis').get(0)

-    list = list.splice 0, 0, toImmutable {
+    list = list.unshift toImmutable {
       category  : 'All'
       iconEmoji : 'clock3'
     }


app/computectrl: remove obsolete "timeout" property :lips: (see: https://github.com/koding/koding/pull/3194)

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -19,7 +19,6 @@
 module.exports = class ComputeController extends KDController

   @providers = globals.config.providers
-  @timeout   = globals.config.COMPUTECONTROLLER_TIMEOUT
   @Error     = {
     'TimeoutError', 'KiteError', 'NotSupported'
     Pending: '107', NotVerified: '500'


Fixed the api route authentication

Fixed the api route authentication

- Shouldn't redirect to login if api route authentication fails. Oauth2
has standard responses, and apps have to be compliant.
- Cleaned up the route setup.

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -5,7 +5,6 @@
 cookieParser = require "cookie-parser"
 passport = require "passport"
 session = require "express-session"
-ensure = require "connect-ensure-login"
 morgan = require "morgan"

 config = require "../config.json"
@@ -46,14 +45,9 @@
 ###

 routes.forEach (rt) ->
-	# TODO Harden the local API routes. Only allow ton-hub to do that.
-	# The bearer API routes are for others to use.
-	# Harden by using client password strategy instead of local strategy?
-	if !rt.auth then app[rt.verb] "/api#{rt.path}", rt.fn
-	else app[rt.verb] "/api#{rt.path}", [
-		ensure.ensureLoggedIn "/login"
-		rt.fn
-	]
+	auth = passport.authenticate "bearer", { session: false }
+	middleware = if !rt.auth then rt.fn else [ rt.fn, auth ]
+	app[rt.verb] "/api#{rt.path}", middleware

 app.all "*", (req, res) ->
 	res.sendFile "index.html", opt


allow group without populated with createdBy, moderator, and member

--- api/services/user.coffee
+++ api/services/user.coffee
@@ -14,15 +14,18 @@
 			""

 	isOwner: (user, group) ->
-		group?.createdBy.id == user?.id
+		owner = group?.createdBy.id || group?.createdBy
+		owner == user?.id

 	isModerator: (user, group) ->
 		_.any group?.moderators, (item) ->
-			item.id == user?.id
+			owner = item.id || item
+			owner == user?.id

 	isMember: (user, group) ->
 		group?.type == 'Unmoderated' or _.any group?.members, (item) ->
-			item.id == user?.id
+			owner = item.id || item
+			owner == user?.id

 	isVisitor: (user, group) ->
 		group?.type == 'Moderated'


Don't forget about the donators!

Don't forget about the donators!


make the hub-proxy server touch project when a proxy is approved

--- src/smc-hub/proxy.coffee
+++ src/smc-hub/proxy.coffee
@@ -93,6 +93,9 @@

     winston.debug("init_http_proxy_server")

+    # Checks for access to project, and in case of write access,
+    # also touch's project thus recording that user is interested
+    # in this project (which sets the last_active time).
     _remember_me_check_for_access_to_project = (opts) ->
         opts = defaults opts,
             project_id  : required
@@ -135,7 +138,10 @@
                                 cb("User does not have write access to project.")
                             else
                                 has_access = true
-                                cb()
+                                # Record that user is going to actively access
+                                # this project.  This is important since it resets
+                                # the idle timeout.
+                                database.touch(account_id:account_id, project_id:opts.project_id, cb:cb)
                 else
                     access.user_has_read_access_to_project
                         project_id : opts.project_id


webapp/smileys: fixing >_<

webapp/smileys: fixing >_<

--- src/smc-util/misc.coffee
+++ src/smc-util/misc.coffee
@@ -1424,7 +1424,8 @@
     opts = exports.defaults opts,
         s           : exports.required
         wrap        : undefined
-    s = opts.s
+    # de-sanitize possible sanitized characters
+    s = opts.s.replace(/>/g, '>').replace(/</g, '<')
     for subs in smileys
         repl = subs[1]
         if opts.wrap


Implement persistence of committees assigned to legislation.

--- jobs/import-legislation.coffee
+++ jobs/import-legislation.coffee
@@ -97,7 +97,22 @@
         callback(err)

   jobs.process 'persist legislation committees', 5, (job, callback) ->
-    callback()
+    legislationId = new ObjectId(job.data.legislationId)
+
+    committeesArray = if job.data.legislationDetail.Committees.CommitteeListing.map?
+      job.data.legislationDetail.Committees.CommitteeListing
+    else
+      [job.data.legislationDetail.Committees.CommitteeListing]
+
+    committeeAssemblyIds = committeesArray.map (committee) ->
+      console.log(committee)
+      Number(committee.Id)
+
+    db.collection("committees").find(assemblyId: {"$in": committeeAssemblyIds}).toArray (err, results) -> ifSuccessful err, callback, ->
+      committeeObjectIds = results.map((_) -> _._id)
+
+      db.collection("legislation").update {_id: legislationId}, {"$set": {"committees": committeeObjectIds}}, (err) ->
+        callback(err)

   jobs.process 'import legislation detail', 5, (job, callback) ->
     # Ensure legislation object ID is in the proper format


fix #199: don't push move events outside chart area

--- app/graph_drawing.coffee
+++ app/graph_drawing.coffee
@@ -98,6 +98,8 @@
     'stroke-linecap': 'square'

 create = (container) ->
+  width = null
+  height = null
   x = d3.time.scale()
   y = d3.scale.linear()
   xAxis = d3.svg.axis().scale(x).orient('bottom')
@@ -132,7 +134,11 @@
   svg = d3.select(container)
     .append('svg')
     .style({'font-family': '"Helvetica Neue"', cursor: 'default'})
-    .on('mousemove', (d, i) -> mouseMoves.push d3.mouse g.node())
+    .on('mousemove', (d, i) ->
+      pos = d3.mouse(g.node())
+      if pos[1] >= 0 and pos[1] <= height
+        mouseMoves.push pos
+    )

   title = svg.append('text')
     .style('text-anchor', 'middle')


Update es-419.coffee

Update es-419.coffee

Some already translated strings appear as untranslated :S

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -343,7 +343,7 @@
     tip_free_your_mind: "Tienes que dejar ir todo, Neo. Miedo, duda, e incredulidad. Libera tu mente. - Morpheus"
     tip_strong_opponents: "Hasta los oponentes mas fuertes siempre tienen una debilidad. - Itachi Uchiha"
     tip_paper_and_pen: "Antes de comenzar a codificar, siempre puedes planear con una hoja de papel y un lapicero."
-#    tip_solve_then_write: "First, solve the problem. Then, write the code. - John Johnson"
+    tip_solve_then_write: "Primero, resuelve el problema. Entonces, escribe el código. - John Johnson"

   game_menu:
     inventory_tab: "Inventario"
@@ -840,18 +840,18 @@
     last_played: "Último jugado"
     leagues_explanation: "Juega en una liga contra otros miembros del clan en estas instancias de arena multijugador."
 #    track_concepts1: "Track concepts"
-#    track_concepts2a: "learned by each student"
-#    track_concepts2b: "learned by each member"
-#    track_concepts3a: "Track levels completed for each student"
-#    track_concepts3b: "Track levels completed for each member"
-#    track_concepts4a: "See your students'"
-#    track_concepts4b: "See your members'"
-#    track_concepts5: "solutions"
+    track_concepts2a: "aprendidos por cada estudiante"
+    track_concepts2b: "aprendidos por cada miembro"
+#    track_concepts3a: "Track levels completados por cada estudiante"
+#    track_concepts3b: "Track levels completados por cada miembro"
+    track_concepts4a: "Ve a tus estudiantes'"
+    track_concepts4b: "Ve a tus miembros'"
+    track_concepts5: "soluciones"
 #    track_concepts6a: "Sort students by name or progress"
 #    track_concepts6b: "Sort members by name or progress"
-#    track_concepts7: "Requires invitation"
-#    track_concepts8: "to join"
-#    private_require_sub: "Private clans require a subscription to create or join."
+    track_concepts7: "Requiere invitación"
+    track_concepts8: "para unirse"
+    private_require_sub: "Los clanes privados requieren una subscripción para crearlos o unírseles."

   courses:
     course: "Curso"
@@ -1263,8 +1263,8 @@
 #    purchase_code1: "Subscription Codes can be redeemed to add premium subscription time to one or more CodeCombat accounts."
 #    purchase_code2: "Each CodeCombat account can only redeem a particular Subscription Code once."
 #    purchase_code3: "Subscription Code months will be added to the end of any existing subscription on the account."
-#    users: "Users"
-#    months: "Months"
+    users: "Usuarios"
+    months: "Meses"
     purchase_total: "Total"
     purchase_button: "Enviar Adquisición"
     your_codes: "Tus Códigos:" # {change}


Fix links

Fix links

--- app/models/header.coffee
+++ app/models/header.coffee
@@ -3,9 +3,9 @@
 module.exports = class Header extends Model
   defaults:
     items: [
-      {href: './test/', title: 'App Tests'},
+      {href: '/test/', title: 'App Tests'},
       {href: 'https://github.com/despairblue/scegratoo', title: 'Docs'},
       {href: 'https://github.com/despairblue/scegratoo/issues', title: 'Github Issues'},
       {href: 'https://github.com/brunch/brunch', title: 'Built with Brunch'},
-      {href: './X3D/', title: 'X3D View'}
+      {href: '/X3D/', title: 'X3D View'}
     ]


DevTools: Keep liveMode state in LocalStorage

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -2,10 +2,16 @@

   COFFEE = "//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"

+  constructor:->
+    super
+
+    @storage = KD.singletons.localStorageController.storage "DevTools"
+    @liveMode = @storage.getAt 'liveMode'
+
   viewAppended:->

     @addSubView @workspace      = new CollaborativeWorkspace
-      name                      : "Kodepad"
+      name                      : "Koding DevTools"
       delegate                  : this
       firebaseInstance          : "tw-local"
       panels                    : [
@@ -25,12 +31,13 @@
           }
           {
             title               : 'Run as I type'
-            cssClass            : "solid live"
+            cssClass            : "solid #{if @liveMode then 'green' else 'live'}"
             callback            : =>
               button = @workspace.panels.first.headerButtons['Run as I type']
               button.unsetClass 'live green'
               button.setClass if @liveMode then 'live' else 'green'
               @liveMode = button.hasClass 'green'
+              @storage.setValue 'liveMode', @liveMode
           }
         ]
         layout                  :


terminal: show error message in a notification if VM start fails

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -400,11 +400,20 @@
     if recentState?.state is 'RUNNING'
       @createNewTab {vm, mode}
     else if recentState?.state is 'STOPPED' or 'FAILED'
-      osKite?.vmOn()
+      osKite?.vmOn().catch @bound "handlePrepareError"
     else
       @notify cssClass : 'error'
       osKite?.vmOff()

+  handlePrepareError: (err) ->
+    console.error err
+
+    try
+      err   = JSON.parse err.message
+      title = err.message  if err.message
+
+    title ?= "VM start failed. Please try again, later"
+    new KDNotificationView {title}

   pistachio: ->
     """


Mockingjay: Added new mock methods for appManager and fsFile.

--- client/mocks/mockingjay.coffee
+++ client/mocks/mockingjay.coffee
@@ -1,6 +1,7 @@
 kd             = require 'kd'
 expect         = require 'expect'
 remote         = require('app/remote').getInstance()
+FSFile         = require 'app/util/fs/fsfile'
 Machine        = require 'app/providers/machine'
 ideRoutes      = require 'ide/routes.coffee'
 dataProvider   = require 'app/userenvironmentdataprovider'
@@ -11,8 +12,8 @@
 mockChannel    = require 'app/util/generateDummyChannel'
 mockThread     = require 'app/util/generateDummyThread'

-mockMachine    = new Machine { machine: mockjmachine }
-{ socialapi }  = kd.singletons
+mockMachine = new Machine { machine: mockjmachine }
+{ socialapi, appManager } = kd.singletons

 module.exports =
@@ -190,6 +191,31 @@
               callback null, [ mockjaccount ]

+  appManager:
+
+    getFrontApp:
+
+      toReturnPassedParam: (param) ->
+
+        expect.spyOn(appManager, 'getFrontApp').andReturn param
+
+
+  fsFile:
+
+    fetchPermissions:
+
+      toReturnError: (error = { some: 'error' }) ->
+
+        expect.spyOn(FSFile.prototype, 'fetchPermissions').andCall (callback) ->
+          callback error
+
+
+      toReturnInfo: (readable = yes, writable = yes) ->
+
+        expect.spyOn(FSFile.prototype, 'fetchPermissions').andCall (callback) ->
+          callback null, { readable, writable }
+
+
   getMockMachine: ->   return mockMachine

   getMockJMachine: ->  return mockjmachine


Removed logging

Removed logging

--- client/terrain/streamer.coffee
+++ client/terrain/streamer.coffee
@@ -37,7 +37,6 @@
 class TerrainTile
   constructor: (@bounds, @pxWidth, onload = null) ->
     @pxHeight = Math.round(@pxWidth/@meterWidth()*@meterHeight())
-    console.log "Dimensions:", @pxWidth, @pxHeight
     @resolution = @pxWidth/@meterWidth()
     @terrainImage = new Image()
     @terrainImage.onload = onload


Fix escaping function

Fix escaping function

--- coffee/lib/view_helpers.coffee
+++ coffee/lib/view_helpers.coffee
@@ -33,6 +33,7 @@
       # We need our NIH function here because underscore do some crappy
       # things with slashes.
       html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
+          .replace(/"/g, """).replace(/'/g, "'")

     formatText: (raw) ->
       ###Format post/comment text.


Always copy the object when boxing

Always copy the object when boxing

--- src/trix/hash.coffee
+++ src/trix/hash.coffee
@@ -68,7 +68,7 @@
     if object instanceof Trix.Hash
       object
     else
-      new Trix.Hash object
+      new Trix.Hash copy(object)

   unbox = (object) ->
     if object instanceof Trix.Hash


triggering change on comprobante and setting comprobante in venta

--- app/assets/javascripts/mixins/comprobante_on_form_mixin.js.coffee
+++ app/assets/javascripts/mixins/comprobante_on_form_mixin.js.coffee
@@ -4,11 +4,14 @@
   ).property()

   comprobanteSelectChanged: (e) ->
-    @set "comprobanteId", e.target.value
+    @get("controller.store").find("comprobante", e.target.value).then (comprobante) =>
+      @set "model.comprobante", comprobante

   didInsertElement: ->
     @_super()
-    @$(".comprobante_select").chosen().on "change", $.proxy(@comprobanteSelectChanged, @)
+    @$(".comprobante_select").chosen().on("change", $.proxy(@comprobanteSelectChanged, @)).
+      trigger("change")
+

   willDestroyElement: ->
     @_super()


Added better url handling for rails assets

--- app/assets/javascripts/sprangular/interceptors/railsAssets.coffee
+++ app/assets/javascripts/sprangular/interceptors/railsAssets.coffee
@@ -4,10 +4,12 @@

   $provide.factory 'railsAssetsInterceptor', ->
     request: (config) ->
-      url = config.url.replace(/^\/assets\//, '')
+      url = config.url.replace(/^[\/]?assets\//, '')

       if assetUrl = Env.templates[url]
         config.url = assetUrl
+      else if url.match(/.html$/)
+        config.url = "/assets/#{url}"

       config


Added cssClass options for tooltip instance.

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -812,6 +812,7 @@
       right      : "w"

     o.title     or= ""
+    o.cssClass  or= ""
     o.placement or= "top"
     o.direction or= "center"
     o.offset    or=


Framework: destroy subviews improvement

Framework: destroy subviews improvement

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -404,11 +404,8 @@
     super

   destroySubViews: ->
-    # (subView.destroy() for subView in @getSubViews())
-
-    for subView in @getSubViews().slice()
-      if subView instanceof KDView
-        subView?.destroy?()
+    view.destroy?() for view in @getSubViews().slice()
+    return

   addSubView:(subView,selector,shouldPrepend)->
     throw new Error 'no subview was specified' unless subView?


CustomPartials: Added CSS support for widgets.

--- client/Main/widgetcontroller.coffee
+++ client/Main/widgetcontroller.coffee
@@ -49,6 +49,22 @@

       if view and key and widgetData and hasPlaceholder
         try
-          view.addSubView eval Encoder.htmlDecode widgetData.partial
+          {css, js}  = widgetData.partial
+          @evalJS    view, js  if js
+          @appendCSS css, key  if css
         catch
           warn "#{key} widget failed to load"
+
+  evalJS: (view, js) ->
+    view.addSubView eval Encoder.htmlDecode js
+
+  appendCSS: (css, key) ->
+    domId         = "#{key}WidgetStyle"
+    oldElement    = document.getElementById domId
+    document.head.removeChild oldElement  if oldElement
+
+    tag           = document.createElement "style"
+    tag.id        = domId
+    tag.innerHTML = css
+
+    document.head.appendChild tag


close screens when user login status changes

--- client/lanes/workspace/Pages.coffee
+++ client/lanes/workspace/Pages.coffee
@@ -11,7 +11,6 @@
         'model.screen_menu_size': { type: 'class' }
         'model.popover_menu': { type: 'booleanClass', name: 'popover-menu' }

-
     subviews:
         menu:
             view: 'ScreensMenu'
@@ -19,26 +18,22 @@
     ui:
         screen: '.screen'

-
     initialize: (options)->
-        this.listenTo( Lanes.Screens.Definitions.displaying, "change:active", this.onActiveChange )
-        this.listenTo( Lanes.Screens.Definitions.displaying, "remove",        this.onRemove )
-        #this.listenTo( @model,'change:screen_menu_position', this.moveScreensMenu )
+        this.listenTo( Lanes.current_user,                   "change:isLoggedIn", this.closeScreens)
+        this.listenTo( Lanes.Screens.Definitions.displaying, "change:active",     this.onActiveChange)
         super

-    # moveScreensMenu: ->
-    #     return unless 'side' == this.viewport.screen_menu_position
-    #     this.ui.screens_menu_container.append( this.viewport.menu_view.el )
+    closeScreens: ->
+        this.ui.screen.children().detach()

-    onRemove: (sv)->
-        sv.view.remove()
+    onRemove: (screen)->
+        screen.view.remove()

     onActiveChange: (screen,active)->
         view = screen.view
         view.parent = this
         unless view.rendered
             view.render()
-
         if active
             this.ui.screen.append( view.el )
         else


Clean up Reporter

Clean up Reporter

--- src/reporter.coffee
+++ src/reporter.coffee
@@ -4,25 +4,25 @@
   'helpers/browser_helper'
   'helpers/url_helper'
 ], (Settings, Promise, BrowserHelper, URLHelper)->
-  unique_id = 1
+  unique_id = 0

   class Reporter
     constructor: (options)->
-      @base = Settings.url.base
-      @queue = []
       @transport = 'img'
-
       @transport_ready = @_determineTransport()

     then: (success, fail) -> @transport_ready.then(success, fail)

-    report: (url, actions) ->
-      promises =
-        for action in actions
-          promise = new Promise()
-          @transport_ready.then =>
-            @_handleJob url, action, promise
-          promise
+    report: (url, data_array) ->
+      promises = []
+
+      for data in data_array
+        promise = new Promise()
+
+        @transport_ready.then =>
+          @_handleJob url, data, promise
+
+        promises.push promise

       Promise.all(promises)

@@ -38,9 +38,9 @@
       ## WE HAVE A VALID URL THAT CONTAINS THE PAYLOAD
       ## WE MUST CHECK THAT THE URL.length IS BELOW THE MAXIMUM ALLOWED
       ## OTHERWISE WE SHOULD CHUNK THE string AND SEND MULTIPLE TRANSPORTS
-      @_createTransport(promise, url)
+      @_createTransport(url, promise)

-    _createTransport: (promise, url) ->
+    _createTransport: (url, promise) ->
       cache_buster = "buster=#{+new Date()}_#{unique_id++}"
       element = document.createElement(@transport)


removing dead code

removing dead code

--- src/rewriter.coffee
+++ src/rewriter.coffee
@@ -28,7 +28,6 @@
     @tagPostfixConditionals()
     @addImplicitBraces()
     @addImplicitParentheses()
-    # @cleanContractExpr()
     @tokens

   # Rewrite the token stream, looking one token ahead and behind.
@@ -254,14 +253,6 @@
       token[0] = 'CONTRACT_SIG'
       1

-  cleanContractExpr: ->
-    inContract = false
-    @scanTokens (token, i, tokens) ->
-      inContract = true if token[0] is 'CONTRACT_SIG'
-      inContract = false if token[0] is 'TERMINATOR'
-
-      token[0] = 'THIS_CONTRACT' if token[1] is '@' and inContract
-      1
   # Generate the indentation tokens, based on another token on the same line.
   indentation: (token, implicit = no) ->
     indent  = ['INDENT', 2, token[2]]


groups: display "create a group" link if user is logged in

`MainController` doesn't emit "accountChanged.to.loggedIn" event because
of window reload on login procedure

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -76,7 +76,7 @@
         router.handleRoute '/Pricing/CreateGroup', entryPoint : 'koding'
         @hide()

-    KD.singleton("mainController").once "accountChanged.to.loggedIn", ->
+    if KD.isLoggedIn()
       KD.singleton("paymentController").fetchSubscriptionsWithPlans tags: ["custom-plan"], (err, subscriptions) ->
         return KD.showError err  if err
         createGroupLink.show()   unless subscriptions.length


Fix sc not being defined

Fix sc not being defined

--- client/angular/components/lmo_textarea/lmo_textarea.coffee
+++ client/angular/components/lmo_textarea/lmo_textarea.coffee
@@ -31,10 +31,8 @@
       successCallback: (res) ->
         $scope.model.newDocumentIds.push(res.documents[0].id)

-
     $scope.modelLength = ->
       $element.find('textarea').val().length
-      upload($scope, $scope.model, {successCallback: sc})(files)

     $scope.addDocument = ($mdMenu) ->
       EventBus.broadcast $scope, 'initializeDocument', Records.documents.buildFromModel($scope.model), $mdMenu


chore(cleanup): remove unused code

chore(cleanup): remove unused code

--- scripts/lib/unpack-lib-server.coffee
+++ scripts/lib/unpack-lib-server.coffee
@@ -14,7 +14,6 @@
 	# consts
 	# ======
 	LIB        = "#{rbRoot}/lib" # lib is created from src
-	NMS        = "node_modules"
 	SERVER     = "src/server"
 	LIB_SERVER = "#{LIB}/#{SERVER}"
 	PKG_NAME   = 'server.tgz'


year needs to be 4 digits

year needs to be 4 digits

--- client/skr/models/CreditCard.coffee
+++ client/skr/models/CreditCard.coffee
@@ -48,8 +48,9 @@
         @year  = year  if year

     dataForSave: (options = {}) ->
-        attrs = super
+        data = super
         # ActiveMerchant uses 'verification_value'
-        attrs.verification_value = attrs.cvc
-        delete attrs.cvc
-        attrs
+        data.verification_value = data.cvc
+        data.year = 2000 + data.year
+        delete data.cvc
+        data


fix spec

fix spec

--- spec/vim-mode-plus-replace-with-execution-spec.coffee
+++ spec/vim-mode-plus-replace-with-execution-spec.coffee
@@ -17,7 +17,7 @@
     waitsForPromise ->
       atom.packages.activatePackage('vim-mode-plus-replace-with-execution')

-  describe "replace-with-execution", ->
+  describe "ReplaceWithExecution", ->
     beforeEach ->
       getVimState (state, vim) ->
         vimState = state
@@ -25,16 +25,12 @@
         {set, ensure, ensureWait} = vim

       runs ->
-        set
-          text: """
-          echo ABC\n
-          """
-          cursor: [0, 0]
+        set textC: "|echo ABC\n"

-    it "replace with execution", ->
-      ensureWait 'V ctrl-r',
-        text: "ABC\n"
+    describe "replace with execution result", ->
+      it "normal", -> ensureWait 'ctrl-r $', text: "ABC\n", mode: 'normal'
+      it "visual", -> ensureWait 'V ctrl-r', text: "ABC\n", mode: 'normal'

-    it "replace with execution and keeping keep original text", ->
-      ensureWait 'V ctrl-R',
-        text: "echo ABC\nABC\n"
+    describe "replace with execution result with keep original text", ->
+      it "normal", -> ensureWait 'ctrl-R $', text: "echo ABC\nABC\n", mode: 'normal'
+      it "visual", -> ensureWait 'V ctrl-R', text: "echo ABC\nABC\n", mode: 'normal'


Revert User Agent variabilization

Revert User Agent variabilization

--- src/sailthru.coffee
+++ src/sailthru.coffee
@@ -10,6 +10,8 @@
 ###
 exports.VERSION = '1.0.8'

+USER_AGENT = 'Sailthru API Node/JavaScript Client'
+
 {SailthruUtil, log} = require './sailthru_util'

 ###
@@ -18,7 +20,6 @@
 class SailthruRequest

     @logging: true # By default enable logging
-    @user_agent: 'Sailthru API Node/JavaScript Client'

     valid_methods = ['GET', 'POST', 'DELETE']

@@ -40,7 +41,7 @@
             method: method
             query: data
             headers:
-                'User-Agent': @user_agent
+                'User-Agent': USER_AGENT
                 Host: parse_uri.host

         http_protocol = if options.port is 443 then https else http
@@ -194,7 +195,7 @@

         rest.post(_url.href + action, {
             multipart: true,
-            'User-Agent': @request.user_agent,
+            'User-Agent': USER_AGENT,
             data: json_payload
         }).on 'complete', (data) ->
             callback null, data


Disable footer links that don't work.

Disable footer links that don't work.

--- src/main/webapp/coffeescripts-hidden/landing.coffee
+++ src/main/webapp/coffeescripts-hidden/landing.coffee
@@ -16,10 +16,12 @@
   return [];

 $(document).ready ->
-  $('.footer-navigation a').tipsy
-    gravity: 'w'
-    fade: true
-    opacity: 0.9
+  $('.footer-navigation a')
+    .click(false)
+    .tipsy
+      gravity: 'w'
+      fade: true
+      opacity: 0.9

   $('body').on 'click', '.get-started-now', ->
     $targetElem = $("section.plans-and-pricing")


Private factoid (#7)

Private factoid (#7)

* Respond in 1-2-1 room

* Add documentation for "search" command

--- src/factoids.coffee
+++ src/factoids.coffee
@@ -42,6 +42,14 @@
       fact.popularity++
       to ?= msg.message.user.name
       msg.send "#{to.trim()}: #{fact.value}"
+
+  robot.respond new RegExp("[#{prefix}]([\\w\\s-]{2,}\\w)", 'i'), (msg) =>
+    fact = @factoids.get msg.match[1]
+    if not fact? or fact.forgotten
+      msg.reply "Not a factoid"
+    else
+      fact.popularity++
+      msg.send "#{fact.value}"

   robot.respond /learn (.{3,}) = ([^@].+)/i, (msg) =>
     [key, value] = [msg.match[1], msg.match[2]]


Update - D:09032021-H:215624

Update - D:09032021-H:215624

--- assets/js/contact.coffee
+++ assets/js/contact.coffee
@@ -6,10 +6,10 @@

 $('#submit').click ->
-  $('#name').val ''
-  $('#subject').val ''
-  $('#email').val ''
-  $('#message').val ''
+  $('#name').val('')
+  $('#subject').val('')
+  $('#email').val('')
+  $('#message').val('')
   return

 # $('#submit').click(function() {


Use session storage instead of local storage

untested

--- core/app/backbone/factlink/local_storage_text_model.coffee
+++ core/app/backbone/factlink/local_storage_text_model.coffee
@@ -4,6 +4,6 @@
   constructor: (attributes, options) ->
     super

-    @set 'text', localStorage?[options.key] ? ''
+    @set 'text', sessionStorage?[options.key] ? ''
     @on 'change:text', (model, value) ->
-      localStorage[options.key] = value if localStorage?
+      sessionStorage[options.key] = value if sessionStorage?


JGroup: If group not found do not broadcast anything

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -472,9 +472,8 @@
     if groupSlug isnt "koding" or event isnt "MemberJoinedGroup"
       @one {slug : groupSlug }, (err, group)=>
         console.error err  if err
-        unless group
-          console.error "No such group with slug: '#{groupSlug}'"
-        else if group.privacy isnt "private" and group.visibility isnt "hidden"
+        return  unless group
+        if group.privacy isnt "private" and group.visibility isnt "hidden"
           unless event is "MemberJoinedGroup" or event is "FollowHappened"
             @oldBroadcast.call this, "koding", event, message
     @oldBroadcast.call this, groupSlug, event, message


groups: use nonce option to validate subscription debiting

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -487,56 +487,22 @@
     {delegate} = client.connection

     {nonce} = formData
-
-    if nonce
-      JPaymentFulfillmentNonce.one {nonce}, (err, nonce) =>
+    delete formData.nonce
+
+    JPaymentFulfillmentNonce.one {nonce}, (err, nonce) =>
+      return callback err  if err
+      return callback "Payment fulfillment nonce is invalid"  unless nonce.action is "debit"
+      nonce.fetchOwner (err, owner) =>
         return callback err  if err
-        return callback "Payment fulfillment nonce is invalid"  unless nonce.action is "debit"
-        nonce.fetchOwner (err, owner) =>
+        subOptions = targetOptions: selector: tags: "custom-plan"
+        owner.fetchSubscription {}, subOptions, (err, subscription) =>
           return callback err  if err
-          subOptions = targetOptions: selector: tags: 'custom-plan'
-          owner.fetchSubscription {}, subOptions, (err, subscription) =>
-            return callback err  if err
-            @create formData, owner, (err, group) ->
-              nonce.update $set: action: "used", (err) ->
+          @create client, formData, owner, (err, group) ->
+            nonce.update $set: action: "used", (err) ->
+              return callback err  if err
+              group.addSubscription subscription, (err) ->
                 return callback err  if err
                 callback null, group, subscription
-      return
-
-    @one slug: 'koding', (err, koding) =>
-      return callback err  if err
-
-      packOptions = targetOptions: selector: tags: 'group'
-
-      koding.fetchPack {}, packOptions, (err, pack) =>
-        return callback err  if err
-        return callback message: "Pack not found!"  unless pack?
-
-        subOptions = targetOptions: selector: tags: 'custom-plan'
-
-        delegate.fetchSubscription {}, subOptions, (err, subscription) =>
-          return callback err  if err
-          unless subscription?
-            return callback message: "Subscription required!"
-
-          subscription.checkUsage pack, (err) =>
-            return callback err  if err
-
-            @create client, formData, delegate, (err, group) ->
-              return callback err  if err
-
-              debitOptions = {
-                pack
-                # avoid creating a nonce, we'll debit the subscription and
-                # create the group all at once.
-                shouldCreateNonce: no
-              }
-
-              subscription.debit debitOptions, (err) ->
-                return callback err  if err
-                group.addSubscription subscription, (err) ->
-                  return callback err  if err
-                  callback null, group, subscription

   @findSuggestions = (client, seed, options, callback)->
     {limit, blacklist, skip}  = options


fix a regression with JGroup#create

fix a regression with JGroup#create

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -26,10 +26,12 @@
   @trait __dirname, '../../traits/taggable'
   @trait __dirname, '../../traits/protected'
   @trait __dirname, '../../traits/joinable'
+  @trait __dirname, '../../traits/slugifiable'

   @share()

   @set
+    slugifyFrom     : 'title'
     feedable        : no
     memberRoles     : ['admin','moderator','member','guest']
     permissions     :
@@ -71,6 +73,9 @@
         type        : String
         validate    : require('../name').validateName
         set         : (value)-> value.toLowerCase()
+      slug_         :
+        type        : String
+        get         : -> @slug
       privacy       :
         type        : String
         enum        : ['invalid privacy type', ['public', 'private']]
@@ -181,48 +186,57 @@
       JMembershipPolicy = require './membershippolicy'
       JName = require '../name'
       {delegate} = client.connection
-      JName.claim formData.slug, [formData.slug], 'JGroup', 'slug', (err)=>
-        if err then callback err
-        else
-          group                 = new this formData
-          permissionSet         = new JPermissionSet
-          defaultPermissionSet  = new JPermissionSet
-          queue = [
-            -> save_ 'group', group, queue, callback
-            -> group.addMember delegate, (err)->
-                if err then callback err
-                else
-                  console.log 'member is added'
-                  queue.next()
-            -> group.addAdmin delegate, (err)->
-                if err then callback err
-                else
-                  console.log 'admin is added'
-                  queue.next()
-            -> save_ 'permission set', permissionSet, queue, callback
-            -> save_ 'default permission set', defaultPermissionSet, queue,
-                      callback
-            -> group.addPermissionSet permissionSet, (err)->
-                if err then callback err
-                else
-                  console.log 'permissionSet is added'
-                  queue.next()
-            -> group.addDefaultPermissionSet defaultPermissionSet, (err)->
-                if err then callback err
-                else
-                  console.log 'permissionSet is added'
-                  queue.next()
-            -> group.addDefaultRoles (err)->
-                if err then callback err
-                else
-                  console.log 'roles are added'
-                  queue.next()
-          ]
-          if 'private' is group.privacy
-            queue.push -> group.createMembershipPolicy -> queue.next()
-          queue.push -> callback null, group
-
-          daisy queue
+      # JName.claim formData.slug, [formData.slug], 'JGroup', 'slug', (err)=>
+      group                 = new this formData
+      permissionSet         = new JPermissionSet
+      defaultPermissionSet  = new JPermissionSet
+
+      queue = [
+        -> group.createSlug (err, slugObj)->
+          if err then callback err
+          else unless slugObj?
+            callback new KodingError "Couldn't claim the slug!"
+          else
+            {slug} = slugObj
+            console.log "slug is claimed: #{slug}"
+            group.slug  = slug
+            group.slug_ = slug
+            queue.next()
+        -> save_ 'group', group, queue, callback
+        -> group.addMember delegate, (err)->
+            if err then callback err
+            else
+              console.log 'member is added'
+              queue.next()
+        -> group.addAdmin delegate, (err)->
+            if err then callback err
+            else
+              console.log 'admin is added'
+              queue.next()
+        -> save_ 'permission set', permissionSet, queue, callback
+        -> save_ 'default permission set', defaultPermissionSet, queue,
+                  callback
+        -> group.addPermissionSet permissionSet, (err)->
+            if err then callback err
+            else
+              console.log 'permissionSet is added'
+              queue.next()
+        -> group.addDefaultPermissionSet defaultPermissionSet, (err)->
+            if err then callback err
+            else
+              console.log 'permissionSet is added'
+              queue.next()
+        -> group.addDefaultRoles (err)->
+            if err then callback err
+            else
+              console.log 'roles are added'
+              queue.next()
+      ]
+      if 'private' is group.privacy
+        queue.push -> group.createMembershipPolicy -> queue.next()
+      queue.push -> callback null, group
+
+      daisy queue

   @findSuggestions = (client, seed, options, callback)->
     {limit, blacklist, skip}  = options


Web Server: Fetch JUser of fake delegate

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -119,12 +119,14 @@
         userEnvironmentData = data
         queue.fin()
     ->
-      client.connection.delegate.fetchUser (err, user) ->
-        console.error err  if err
-        unless user
-          console.error {message: "[scriptblock] user not found"}
-        else
-          userId = user.getId()
+      {nickname} = client.connection.delegate.profile
+      bongoModels.JUser.one username: nickname, (err, user) ->
+        if err
+          console.error err
+          return queue.fin()
+
+        if user then userId = user.getId()
+        else console.error message: "[scriptblock] user not found"
         queue.fin()
   ]


[LoadingFree] add everything facet into options

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -25,7 +25,9 @@
   fetchActivityFromGraph = (cb)->
     return cb null, [] unless bongoModels
     {CActivity} = bongoModels
-    CActivity._fetchPublicActivityFeed client, {}, (err, data)->
+    options = facets : "Everything"
+
+    CActivity._fetchPublicActivityFeed client, options, (err, data)->
       return cb null, [] if err
       return cb null, data


Treat empty string as blank 0x0 SVG tile (fix #30)

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -123,7 +123,9 @@
     else
       new StaticSymbol key,
         if typeof data == 'string'
-          if data.indexOf('<') < 0  ## No <'s -> interpret as filename
+          if data.trim() == ''  ## Blank SVG treated as 0x0 symbol
+            svg: '<symbol viewBox="0 0 0 0"/>'
+          else if data.indexOf('<') < 0  ## No <'s -> interpret as filename
             extension = extensionOf data
             ## <image> tag documentation: "Conforming SVG viewers need to
             ## support at least PNG, JPEG and SVG format files."


:lipstick:

:lipstick:

--- src/theme-manager.coffee
+++ src/theme-manager.coffee
@@ -6,7 +6,7 @@
 {File} = require 'pathwatcher'
 fs = require 'fs-plus'
 Q = require 'q'
-{deprecate} = require 'grim'
+Grim = require 'grim'

 Package = require './package'

@@ -101,17 +101,17 @@
   on: (eventName) ->
     switch eventName
       when 'reloaded'
-        deprecate 'Use ThemeManager::onDidReloadAll instead'
+        Grim.deprecate 'Use ThemeManager::onDidReloadAll instead'
       when 'stylesheet-added'
-        deprecate 'Use ThemeManager::onDidAddStylesheet instead'
+        Grim.deprecate 'Use ThemeManager::onDidAddStylesheet instead'
       when 'stylesheet-removed'
-        deprecate 'Use ThemeManager::onDidRemoveStylesheet instead'
+        Grim.deprecate 'Use ThemeManager::onDidRemoveStylesheet instead'
       when 'stylesheet-updated'
-        deprecate 'Use ThemeManager::onDidUpdateStylesheet instead'
+        Grim.deprecate 'Use ThemeManager::onDidUpdateStylesheet instead'
       when 'stylesheets-changed'
-        deprecate 'Use ThemeManager::onDidChangeStylesheets instead'
+        Grim.deprecate 'Use ThemeManager::onDidChangeStylesheets instead'
       else
-        deprecate 'ThemeManager::on is deprecated. Use event subscription methods instead.'
+        Grim.deprecate 'ThemeManager::on is deprecated. Use event subscription methods instead.'
     EmitterMixin::on.apply(this, arguments)

   ###


properly detach touch events on zoom tool

--- lib/assets/javascripts/zui53/tools/zoom_tool.js.coffee
+++ lib/assets/javascripts/zui53/tools/zoom_tool.js.coffee
@@ -20,12 +20,14 @@
       @eventDispatcher.addEventListener 'gesturestart', @gesture_start, @use_capture

       @eventDispatcher.addEventListener 'MozTouchDown', @moz_touch_down, @use_capture
-
       @eventDispatcher.addEventListener 'MozTouchUp', @moz_touch_up, @use_capture

     detach: ()=>
       $(@eventDispatcher).unmousewheel @zoom
       @eventDispatcher.removeEventListener 'gesturestart', @gesture_start, @use_capture
+
+      @eventDispatcher.removeEventListener 'MozTouchDown', @moz_touch_down, @use_capture
+      @eventDispatcher.removeEventListener 'MozTouchUp', @moz_touch_up, @use_capture


group create modal: setting defaults aaaaaaand removed group vm and members vm inputs

--- client/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/app/Applications/Groups.kdapplication/AppController.coffee
@@ -351,6 +351,28 @@
         else
           modal.modalTabs.forms["General Settings"].inputs.Slug.unsetClass 'slug-taken'

+    getGroupType = ->
+      modal.modalTabs.forms["Select group type"].inputs.type.getValue()
+
+    getPrivacyDefault = ->
+      switch getGroupType()
+        when 'educational'  then 'by-request'
+        when 'company'      then 'by-invite'
+        when 'project'      then'public'
+        when 'custom'       then 'public'
+
+    getVisibilityDefault = ->
+      switch getGroupType()
+        when 'educational'  then 'visible'
+        when 'company'      then 'hidden'
+        when 'project'      then 'visible'
+        when 'custom'       then 'visible'
+
+    applyDefaults =->
+      {Privacy,Visibility} = modal.modalTabs.forms["General Settings"].inputs
+      Privacy.setValue getPrivacyDefault()
+      Visibility.setValue getVisibilityDefault()
+
     modalOptions =
       title                          : 'Create a new group'
       height                         : 'auto'
@@ -373,6 +395,7 @@
               "Next"                 :
                 style                : "modal-clean-gray"
                 type                 : "submit"
+                callback             : -> applyDefaults()
             fields                   :
               "type"                 :
                 name                 : "type"
@@ -453,16 +476,21 @@
                   { title : "Visible in group listings",    value : "visible" }
                   { title : "Hidden in group listings",     value : "hidden" }
                 ]
-              "Group VM"             :
-                label                : "Create a shared server for the group"
-                itemClass            : KDOnOffSwitch
-                name                 : "group-vm"
-                defaultValue         : no
-              "Member VM"            :
-                label                : "Create a server for each group member"
-                itemClass            : KDOnOffSwitch
-                name                 : "member-vm"
-                defaultValue         : no
+              # # Group VM should be there for every group
+              #
+              # "Group VM"             :
+              #   label                : "Create a shared server for the group"
+              #   itemClass            : KDOnOffSwitch
+              #   name                 : "group-vm"
+              #   defaultValue         : no
+              #
+              # # Members VMs are a future feature
+              #
+              # "Member VM"            :
+              #   label                : "Create a server for each group member"
+              #   itemClass            : KDOnOffSwitch
+              #   name                 : "member-vm"
+              #   defaultValue         : no

     modal = new KDModalViewWithForms modalOptions


added affix option to fixed toolbar

added affix option to fixed toolbar

--- src/toolbar/fixed.coffee
+++ src/toolbar/fixed.coffee
@@ -12,6 +12,9 @@
       editable: null
       toolbar: null

+      affix: true
+      affixTopOffset: 2
+
     _create: ->
       @toolbar = @options.toolbar
       @toolbar.show()
@@ -21,6 +24,8 @@
       @_bindEvents()

       jQuery(window).resize (event) =>
+        @setPosition()
+      jQuery(window).scroll (event) =>
         @setPosition()

       # Make sure the toolbar has not got the full width of the editable
@@ -60,7 +65,18 @@
       return unless @options.parentElement is 'body'
       @toolbar.css 'position', 'absolute'
       @toolbar.css 'top', @element.offset().top - @toolbar.outerHeight()
-      @toolbar.css 'left', @element.offset().left + 10
+
+      if @options.affix
+        scrollTop = jQuery(window).scrollTop()
+        elementTop = @element.offset().top - (@toolbar.height() + @options.affixTopOffset)
+        elementBottom = (@element.height() - @options.affixTopOffset) + (@element.offset().top - @toolbar.height())
+
+        if scrollTop > elementTop && scrollTop < elementBottom
+          @toolbar.css('position', 'fixed')
+          @toolbar.css('top', @options.affixTopOffset)
+      else
+
+      @toolbar.css 'left', @element.offset().left - 2

     _updatePosition: (position) ->
       return


Allow nesting of scrollable containers by stopping propagation. Fixes #1178

--- core/app/assets/javascripts/lib/prevent_scroll_propagation.coffee
+++ core/app/assets/javascripts/lib/prevent_scroll_propagation.coffee
@@ -3,6 +3,8 @@
 $.fn.preventScrollPropagation = ->
   @each ->
     $(this).bind 'mousewheel', (event, delta) ->
+      event.stopPropagation() # allow nesting of scrollable containers
+
       if delta > 0 && $(this).scrollTop() <= 0
         event.preventDefault()
       else if delta < 0 && $(this).scrollTop() >= $(this).get(0).scrollHeight - $(this).innerHeight()


export scroll to mixin for easy import

export scroll to mixin for easy import

--- index.cjsx
+++ index.cjsx
@@ -19,6 +19,7 @@

   FreeResponse:           require './src/components/exercise/free-response'
   GetPositionMixin:       require './src/components/get-position-mixin'
+  ScrollToMixin:          require './src/components/scroll-to-mixin'
   KeysHelper:             require './src/helpers/keys'
   NotificationActions:    require './src/model/notifications'
   NotificationsBar:       require './src/components/notifications/bar'


fix font-size

fix font-size


Remove useless button.

Remove useless button.

--- index.cjsx
+++ index.cjsx
@@ -213,11 +213,6 @@
           <hr />
         </div>
         <Grid>
-          <Col xs=6>
-            <Button bsStyle='warning' style={width: '100%'} onClick={@handleRefresh}>
-              {__ 'Reset'}
-            </Button>
-          </Col>
         </Grid>

       </div>


photos and gallery are now working again

--- Modules/PhotoPicker/PhotoPickerTable.coffee
+++ Modules/PhotoPicker/PhotoPickerTable.coffee
@@ -31,7 +31,7 @@
       onRemove: (photo, thumbnail) =>
         @photos = @photos.without photo
         @thumbnails = @thumbnails.without thumbnail
-        @update()
+        @update() #TODO: GJ: should we delete files?
     }
     @view.add @grid.view
     @update()
@@ -59,7 +59,7 @@

     @photos.push url
     @thumbnails.push url
-    @grid.addPhoto url
+    @grid.addPhoto url, url
     @update()

   addMedia: (image) =>
@@ -72,7 +72,7 @@

     @photos.push filename
     @thumbnails.push thumbfilename
-    @grid.addPhoto thumb, filename
+    @grid.addPhoto thumbfilename, filename
     @update()

   saveImage: (image, filename) ->


altering redirect algorithm to allow hal

--- noddy/service/oabutton/redirect.coffee
+++ noddy/service/oabutton/redirect.coffee
@@ -18,10 +18,10 @@
           source = url
           if listing.fulltext
             # switch the url by comparing the fulltext and splash examples, and converting the url in the same way
-            start = listing.splash.split(listing.identifier)[0]
-            url = url.replace start, listing.fulltext.split(listing.identifier)[0]
-            end = listing.splash.split(listing.identifier)[1]
-            url = url.replace(end, listing.fulltext.split(listing.identifier)[1]) if end
+            parts = listing.splash.split(listing.identifier)[0]
+            diff = url.replace parts[0], ''
+            diff = diff.replace(parts[1]) if parts.length > 1
+            url = listing.fulltext.replace litsing.identifier, diff
           else if listing.element and url.indexOf('.pdf') is -1
             try
               content = API.http.phantom url


Focus first text editor (termSelect)

Focus first text editor (termSelect)

--- lib/GUI/atox-termSelect.coffee
+++ lib/GUI/atox-termSelect.coffee
@@ -50,6 +50,12 @@
     else
       return @list.val()

+  focus: ->
+    if @inputType is 'editor'
+      return @editor.focus()
+    else
+      return @list.focus()
+
 class GetArgs extends View
   @content: (params) ->
     @div class: 'atox-termSelectGetArgs', =>
@@ -89,6 +95,7 @@
       @args.append  arg

     @panel.show()
+    @argsOBJ[0].focus()

   run: ->
     @panel.hide()


Require login before opening preferences

--- lib/actions/preferences.coffee
+++ lib/actions/preferences.coffee
@@ -1,5 +1,6 @@
 open = require('open')
 config = require('../config')
+authHooks = require('../hooks/auth')

-exports.preferences = ->
+exports.preferences = authHooks.failIfNotLoggedIn ->
 	open(config.urls.preferences)


remove trash

remove trash

--- lib/carte/client/router.coffee
+++ lib/carte/client/router.coffee
@@ -4,7 +4,6 @@
 module.exports = class Router extends Backbone.Router
   routes:
     '': 'list'
-    'slideshow': 'slideshow'
     ':title': 'show'

   list: (string)->
@@ -18,8 +17,3 @@
     @current = 'show'
     @title = title
     @query = querystring.parse(string)
-
-  slideshow: (string)->
-    console.log 'slideshow', string
-    @current = 'slideshow'
-    @query = querystring.parse(string)


Restore the componentsOf tests for now

Restore the componentsOf tests for now

--- test/english.coffee
+++ test/english.coffee
@@ -1,4 +1,5 @@
 humanizing = require '..'
+componentsOf = humanizing.componentsOf
 require('chai').should()

 describe 'English humanization of duration', ->
@@ -155,3 +156,36 @@
     humanizing(2.minutes() + 18.seconds() + 1, { delimiter: 'X' })
       .should.equal '2 minutesX18 secondsX1 millisecond'
     humanizing.defaults.delimiter = oldDelimiter
+
+  describe 'componentsOf', ->
+
+    it 'can grab the components', ->
+      duration = 5.days() + 126.minutes() + 4.seconds() + 20.milliseconds()
+      components = componentsOf duration
+      components.years.should.equal '0 years'
+      components.total.years.should.equal '0 years'
+      components.months.should.equal '0 months'
+      components.total.months.should.equal '0 months'
+      components.days.should.equal '5 days'
+      components.total.days.should.equal '5 days'
+      components.hours.should.equal '2 hours'
+      components.total.hours.should.equal '122 hours'
+      components.minutes.should.equal '6 minutes'
+      components.total.minutes.should.equal '7326 minutes'
+      components.seconds.should.equal '4 seconds'
+      components.total.seconds.should.equal '439564 seconds'
+      components.milliseconds.should.equal '20 milliseconds'
+      components.total.milliseconds.should.equal '439564020 milliseconds'
+
+    it 'can grab components of fractioned milliseconds', ->
+      duration = 420.69
+      components = componentsOf duration
+      components.seconds.should.equal '0 seconds'
+      components.total.seconds.should.equal '0 seconds'
+      components.milliseconds.should.equal '420.69 milliseconds'
+      components.total.milliseconds.should.equal '420.69 milliseconds'
+
+    it 'grabs the components and keeps the Number objects intact', ->
+      duration = new Number(420420)
+      componentsOf duration
+      duration.valueOf().should.equal 420420