Uncomment MAX_SOCKETS

Uncomment MAX_SOCKETS

--- desktop/lib/setup.coffee
+++ desktop/lib/setup.coffee
@@ -91,11 +91,10 @@

   # Increase max sockets. The goal of this is to improve app -> api
   # performance but the downside is it limits client connection reuse with keep-alive
-  # console.log typeof MAX_SOCKETS
-  # if typeof MAX_SOCKETS == 'number' and MAX_SOCKETS > 0
-  #   http.globalAgent.maxSockets = MAX_SOCKETS
-  # else
-  #   http.globalAgent.maxSockets = Number.MAX_VALUE
+  if typeof MAX_SOCKETS == 'number' and MAX_SOCKETS > 0
+    http.globalAgent.maxSockets = MAX_SOCKETS
+  else
+    http.globalAgent.maxSockets = Number.MAX_VALUE

   # Override Backbone to use server-side sync, inject the XAPP token,
   # add redis caching, and augment sync with Q promises.


修改模块上传成功后的提示语

修改模块上传成功后的提示语

--- lib/module/publish-module-view.coffee
+++ lib/module/publish-module-view.coffee
@@ -59,7 +59,7 @@
           @progress class: 'inline-block'
       @div outlet: "moduleApplyView",class: 'form-horizontal form_width',=>
         @div class:"text-center", =>
-          @span outlet:"getAppListTipsView","模块上传成功过，检测到以下应用已关联本模块，是否应用为模块的最新版本？"
+          @span outlet:"getAppListTipsView","模块上传成功，检测到以下应用已关联本模块，是否应用为模块的最新版本？"
           @br
         @div outlet:"tableView", =>
           @div outlet: "appListtable",=>


proper logic

proper logic

--- apps/order/routes.coffee
+++ apps/order/routes.coffee
@@ -9,7 +9,7 @@
       res.redirect '/'

 @resume = (req, res) ->
-  unless req.user or ((token = req.query.token) && (orderId = req.params.id))
+  unless ((token = req.query.token) && (orderId = req.params.id)) and req.user
     return res.redirect '/'
   req.user.resumeOrder orderId, token,
     success: ->


Update zh-HANS.coffee

Update zh-HANS.coffee

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -2451,7 +2451,7 @@
     attack_capitalization: "攻击 $1，而不是 $2。  (重要：请区分大小写。)"
 #    empty_while: "Empty while statement. Put 4 spaces in front of statements inside the while statement."
 #    line_of_site: "`$1`'s argument `$2` has a problem. Is there an enemy within your line-of-sight yet?"
-#    need_a_after_while: "Need a `$1` after `$2`."
+    need_a_after_while: "在`$2`后面需要一个`$1`。"
 #    too_much_indentation: "Too much indentation at the beginning of this line."
 #    missing_hero: "Missing `$1` keyword; should be `$2`."
 #    takes_no_arguments: "`$1` takes no arguments."
@@ -2491,13 +2491,13 @@
 #    unclosed_paren_in_function_arguments: "Unclosed $1 in function arguments."
 #    unexpected_end_of_input: "Unexpected end of input"
 #    there_is_no_enemy: "There is no `$1`. Use `$2` first." # Hints start here
-#    try_herofindnearestenemy: "Try `$1`"
-#    there_is_no_function: "There is no function `$1`, but `$2` has a method `$3`."
+    try_herofindnearestenemy: "试一试 `$1`"
+    there_is_no_function: "没有`$1`函数，但是`$2`有`$3`函数。"
 #    attacks_argument_enemy_has: "`$1`'s argument `$2` has a problem."
 #    is_there_an_enemy: "Is there an enemy within your line-of-sight yet?"
-#    target_is_null_is: "Target is $1. Is there always a target to attack? (Use $2?)"
-#    hero_has_no_method: "`$1` has no method `$2`."
-#    there_is_a_problem: "There is a problem with your code."
+#    target_is_null_is: "目标$1出现了问题（可能是null）。 Is there always a target to attack? (Use $2?)"
+    hero_has_no_method: "`$1` 没有 `$2` 函数。"
+    there_is_a_problem: "你的代码有问题。"
 #    did_you_mean: "Did you mean $1? You do not have an item equipped with that skill."
 #    missing_a_quotation_mark: "Missing a quotation mark. "
 #    missing_var_use_var: "Missing `$1`. Use `$2` to make a new variable."


zh-hant

zh-hant

translation

--- app/locale/zh-HANT.coffee
+++ app/locale/zh-HANT.coffee
@@ -843,89 +843,89 @@
     last_played: "最後玩了"
     leagues_explanation: "在部落裡與其他成員組成聯盟一起參加下面的多人競技場。"

-#  courses:
-#    course: "Course"
-#    courses: "courses"
-#    not_enrolled: "You are not enrolled in this course."
-#    visit_pref: "Please visit the"
-#    visit_suf: "page to enroll."
-#    select_class: "Select one of your classes"
-#    unnamed: "*unnamed*"
-#    select: "Select"
-#    unnamed_class: "Unnamed Class"
-#    edit_settings: "edit class settings"
-#    edit_settings1: "Edit Class Settings"
-#    progress: "Class Progress"
-#    add_students: "Add Students"
-#    stats: "Statistics"
-#    total_students: "Total students:"
-#    average_time: "Average level play time:"
-#    total_time: "Total play time:"
-#    average_levels: "Average levels completed:"
-#    total_levels: "Total levels completed:"
-#    furthest_level: "Furthest level completed:"
-#    concepts_covered: "Concepts Covered"
-#    students: "Students"
-#    students1: "students"
-#    expand_details: "Expand details"
-#    concepts: "Concepts"
-#    levels: "levels"
-#    played: "Played"
-#    play_time: "Play time:"
-#    completed: "Completed:"
-#    invite_students: "Invite students to join this class."
-#    enter_emails: "Enter student emails to invite, one per line"
-#    send_invites: "Send Invites"
-#    title: "Title"
-#    description: "Description"
-#    languages_available: "Select programming languages available to the class:"
-#    all_lang: "All Languages"
-#    show_progress: "Show student progress to everyone in the class"
-#    creating_class: "Creating class..."
-#    purchasing_course: "Purchasing course..."
-#    buy_course: "Buy Course"
-#    buy_course1: "Buy this course"
-#    create_class: "Create Class"
-#    select_all_courses: "Select 'All Courses' for a 50% discount!"
-#    all_courses: "All Courses"
-#    number_students: "Number of students"
-#    enter_number_students: "Enter the number of students you need for this class."
-#    name_class: "Name your class"
-#    displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
-#    buy: "Buy"
-#    purchasing_for: "You are purchasing a license for"
-#    creating_for: "You are creating a class for"
-#    for: "for" # Like in 'for 30 students'
-#    receive_code: "Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class."
-#    free_trial: "Free trial for teachers!"
-#    get_access: "to get individual access to all courses for evalutaion purposes."
-#    questions: "Questions?"
-#    faq: "Courses FAQ"
-#    question: "Q:" # Like in 'Question'
-#    question1: "What's the difference between these courses and the single player game?"
-#    answer: "A:" # Like in 'Answer'
-#    answer1: "The single player game is designed for individuals, while the courses are designed for classes."
-#    answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
-#    teachers_click: "Teachers Click Here"
-#    students_click: "Students Click Here"
-#    courses_on_coco: "Courses on CodeCombat"
-#    designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
-#    more_in_less: "Learn more in less time"
-#    no_experience: "No coding experience necesssary"
-#    easy_monitor: "Easily monitor student progress"
-#    purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!"
-#    see_the: "See the"
-#    more_info: "for more information."
-#    choose_course: "Choose Your Course:"
-#    enter_code: "Enter an unlock code"
-#    enter_code1: "Enter unlock code"
-#    enroll: "Enroll"
-#    pick_from_classes: "Pick from your current classes"
-#    enter: "Enter"
-#    or: "Or"
-#    topics: "Topics"
-#    hours_content: "Hours of content:"
-#    get_free: "Get FREE course"
+  courses:
+    course: "課程"
+    courses: "課程"
+    not_enrolled: "您没有註冊這一節課"
+    visit_pref: "請到這個"
+    visit_suf: "網頁註冊"
+    select_class: "請選其中一門課堂"
+    unnamed: "*未命名*"
+    select: "選擇"
+    unnamed_class: "課堂未命名"
+    edit_settings: "編輯課堂設定"
+    edit_settings1: "編輯課堂設定"
+    progress: "課堂進度"
+    add_students: "添加學生"
+    stats: "統計"
+    total_students: "學生人數："
+    average_time: "平均遊戲時間："
+    total_time: "總計遊戲時間："
+    average_levels: "平均完成關卡："
+    total_levels: "總共完成關卡："
+    furthest_level: "最高關卡完成："
+    concepts_covered: "課目覆蓋"
+    students: "學生"
+    students1: "個學生。"
+    expand_details: "展開細節"
+    concepts: "課目"
+    levels: "關卡"
+    played: "已通關"
+    play_time: "遊戲時間:"
+    completed: "完成:"
+    invite_students: "邀請學生加入此課堂。"
+    enter_emails: "輸入學生電郵地址來邀請，每行一個。"
+    send_invites: "發送邀請"
+    title: "課堂名字"
+    description: "描述"
+    languages_available: "Select programming languages available to the class:"
+    all_lang: "所有編程語言"
+    show_progress: "向所有該課堂的人展示學生的進度"
+    creating_class: "課堂創建中···"
+    purchasing_course: "購買課程中···"
+    buy_course: "購買課程"
+    buy_course1: "購買這個課程"
+    create_class: "創建課堂"
+    select_all_courses: "可半價一次購買所有課程！"
+    all_courses: "所有課程"
+    number_students: "學生人數"
+    enter_number_students: "輪入該課堂的學生上限人數"
+    name_class: "命名您的課堂"
+    displayed_course_page: "這將會在課程頁面顯示，可被修改。"
+    buy: "購買"
+    purchasing_for: "You are purchasing a license for"
+    creating_for: "您正在創建一個課程為"
+    for: "，人數上限為" # Like in 'for 30 students'
+    receive_code: "然後您會收到一個解鎖碼，把它分發給你的學生用來註冊你的課堂。"
+    free_trial: "老師可免費試用！"
+    get_access: "to get individual access to all courses for evalutaion purposes."
+    questions: "Questions?"
+    faq: "課程FAQ"
+    question: "問：" # Like in 'Question'
+    question1: "What's the difference between these courses and the single player game?"
+    answer: "答：" # Like in 'Answer'
+    answer1: "The single player game is designed for individuals, while the courses are designed for classes."
+    answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
+    teachers_click: "老師點擊這裡"
+    students_click: "學生點擊這裡"
+    courses_on_coco: "CodeCombat上的課程"
+    designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
+    more_in_less: "以最少的時間學習最多的知識"
+    no_experience: "無需編程經驗"
+    easy_monitor: "容易監控學生的進程"
+    purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!"
+    see_the: "詳細資訊請看"
+    more_info: "。"
+    choose_course: "選擇您的課程："
+    enter_code: "輸入一個解銷碼"
+    enter_code1: "輸入解銷碼"
+    enroll: "註冊"
+    pick_from_classes: "從目前的課程選擇"
+    enter: "輪入"
+    or: "或"
+    topics: "題目"
+    hours_content: "內容時間："
+    get_free: "取得免費課程！"

   classes:
     archmage_title: "大法師"


Big fixes

Big fixes

--- app/scripts/shared.coffee
+++ app/scripts/shared.coffee
@@ -69,9 +69,9 @@
   }

   # Creates default localStorage values if they don't already exist
-  Object.keys(defaultValues).forEach (name) ->
-    if !localStorage.getItem(name)?
-      localStorage.setItem(name, defaultValues[name])
+  for name, value of defaultValues
+    if !localStorage.getItem(name)? || localStorage.getItem(name) == "undefined"
+      localStorage.setItem(name, value)

 # Initialisation routines for all pages


[skip] Deleted $.fn.appendView because Houston defines it

--- app/assets/javascripts/houston-scheduler/helpers/jquery_extensions.coffee
+++ app/assets/javascripts/houston-scheduler/helpers/jquery_extensions.coffee
@@ -1,9 +1,4 @@
 $.fn.extend
-
-  appendView: (view)->
-    @.append(view.el)
-    view.render()
-    @

   cssHover: (selector)->
     if arguments.length == 0


Update source to latest in `coffeelint`

Update source to latest in `coffeelint`

--- src/RuleProcessor.coffee
+++ src/RuleProcessor.coffee
@@ -1,23 +1,41 @@
-regexes =
-  nonEnglishOperators: /[&|\||\=]{2}|\!\=/
-
 module.exports = class RuleProcessor
   rule:
     name: 'prefer_english_operator'
     description: '''
-      This rule prohibits &&, ||, == and !=.
-      Use and, or, is, and isnt instead.
-      '''
-    level: 'warn'
-    message: 'Don\'t use &&, ||, == and !='
+    This rule prohibits &&, ||, ==, != and !.
+    Use and, or, is, isnt, and not instead.
+    !! for converting to a boolean is ignored.
+    '''
+    level: 'ignore'
+    doubleNotLevel: 'ignore'
+    message: 'Don\'t use &&, ||, ==, !=, or !'

-  lintLine: (line, lineApi) ->
-    lineTokens = lineApi.getLineTokens()
+  tokens: ['COMPARE', 'UNARY_MATH', 'LOGIC']
+  lintToken: (token, tokenApi) ->
+    config = tokenApi.config[@rule.name]
+    level = config.level
+    # Compare the actual token with the lexed token.
+    { first_column, last_column } = token[2]
+    line = tokenApi.lines[tokenApi.lineNumber]
+    actual_token = line[first_column..last_column]
+    context =
+      switch actual_token
+        when '==' then 'Replace "==" with "is"'
+        when '!=' then 'Replace "!=" with "isnt"'
+        when '||' then 'Replace "||" with "or"'
+        when '&&' then 'Replace "&&" with "and"'
+        when '!'
+          # `not not expression` seems awkward, so `!!expression`
+          # gets special handling.
+          if tokenApi.peek(1)?[0] is 'UNARY_MATH'
+            level = config.doubleNotLevel
+            '"?" is usually better than "!!"'
+          else if tokenApi.peek(-1)?[0] is 'UNARY_MATH'
+            # Ignore the 2nd half of the double not
+            undefined
+          else
+            'Replace "!" with "not"'
+        else undefined

-    for token in lineTokens
-      if token[0] in ['COMPARE', 'LOGIC']
-        location = token[2]
-        substring = line[location.first_column..location.last_column]
-        hasNonEnglishOperators = substring.match regexes.nonEnglishOperators
-        if hasNonEnglishOperators
-          return {context: "Found: #{hasNonEnglishOperators[0]}"}
+    if context?
+      { level, context }


Don't load accessibility data in embeds.

--- src/accessibility.coffee
+++ src/accessibility.coffee
@@ -5,6 +5,9 @@
     _,
     Backbone
 ) ->
+
+    if appSettings.is_embedded
+        return null

     class Accessibility
         constructor: ->


Update directory

Update directory

--- src/app/directory.coffee
+++ src/app/directory.coffee
@@ -12,24 +12,26 @@
 class Directory
   path: null

-  # Public: Creates a new directory.
+  ### Public ###
+
+  # Creates a new directory.
   #
   # path - A {String} representing the file directory
   # symlink - A {Boolean} indicating if the path is a symlink (default: false)
   constructor: (@path, @symlink=false) ->

-  # Public: Retrieves the basename of the directory.
+  # Retrieves the basename of the directory.
   #
   # Returns a {String}.
   getBaseName: ->
     fsUtils.base(@path)

-  # Public: Retrieves the directory's path.
+  # Retrieves the directory's path.
   #
   # Returns a {String}.
   getPath: -> @path

-  # Public: Retrieves the file entries in the directory.
+  # Retrieves the file entries in the directory.
   #
   # This does follow symlinks.
   #


:horse_racing: Use buffer ranges to see if selections intersect

--- src/app/selection.coffee
+++ src/app/selection.coffee
@@ -407,7 +407,7 @@
     @getBufferRange().intersectsWith(bufferRange)

   intersectsWith: (otherSelection) ->
-    @getScreenRange().intersectsWith(otherSelection.getScreenRange())
+    @getBufferRange().intersectsWith(otherSelection.getBufferRange())

   merge: (otherSelection, options) ->
     @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)


formatCamel regex function now capitalizes first letter

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -32,7 +32,9 @@

 #Something for Everyone
 format = (str, del) -> str.replace(/\s+/g, del || '-').toLowerCase()
-formatCamel = (str) -> str.replace(/([^A-Za-z0-9\.\$])|([A-Z])(?=[A-Z][a-z])|([^\-\$\.0-9])(?=\$?[0-9]+(?:\.[0-9]+)?)|([0-9])(?=[^\.0-9])|([a-z])(?=[A-Z])/g, '$2$3$4$5 ')
+formatCamel = (str) ->
+  str = str.replace(/([^A-Za-z0-9\.\$])|([A-Z])(?=[A-Z][a-z])|([^\-\$\.0-9])(?=\$?[0-9]+(?:\.[0-9]+)?)|([0-9])(?=[^\.0-9])|([a-z])(?=[A-Z])/g, '$2$3$4$5 ')
+  str.charAt(0).toUpperCase() + str.slice(1)

 @currentPage = new ReactiveVar()

@@ -40,6 +42,7 @@
   for group in bootstrap_magic_variables
     if group.keyName is currentPage.get()
       return group
+    console.log "category: "+ formatCamel group.category

 initColorPicker = (node) ->


clean up surety guard in task plan footer

--- tutor/src/components/task-plan/footer.cjsx
+++ tutor/src/components/task-plan/footer.cjsx
@@ -141,7 +141,6 @@

       deleteLink =
         <SuretyGuard
-          onConfirm={@publishExercise}
           onConfirm={@onDelete}
           okButtonLabel='Yes'
           placement='top'


Cleanup whitespace

Cleanup whitespace

--- client/landing/app/Applications/Topics.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Topics.kdapplication/AppController.coffee
@@ -1,5 +1,5 @@
 class Topics12345 extends AppController
-
+
   constructor:(options, data)->
     options = $.extend
       # view : if /localhost/.test(location.host) then new TopicsMainView cssClass : "content-page topics" else new TopicsComingSoon
@@ -8,26 +8,26 @@
     ,options
     super options,data
     @controllers = {}
-
+
   bringToFront:()->
     @propagateEvent (KDEventType : 'ApplicationWantsToBeShown', globalEvent : yes),
       options :
         name : 'Topics'
       data : @getView()
-
+
   initAndBringToFront:(options,callback)->
     @bringToFront()
     callback()
-
+
   createFeed:(view)->
     appManager.tell 'Feeder', 'createContentFeedController', {
       subItemClass          : TopicsListItemView
       limitPerPage          : 20
-      # feedMessage           :
+      # feedMessage           :
       #   title                 : "Topics organize shared content on Koding. Tag items when you share, and follow topics to see content relevant to you in your activity feed."
       #   messageLocation       : 'Topics'
       help                  :
-        subtitle            : "Learn About Topics"
+        subtitle            : "Learn About Topics"
         tooltip             :
           title             : "
Topic Tags organize content that users share on Koding. Follow the topics you are interested in and we'll include the tagged items in your activity feed.
"
           placement         : "above"
@@ -61,9 +61,9 @@
     mainView.createCommons()
     @createFeed mainView
     # mainView.on "AddATopicFormSubmitted",(formData)=> @addATopic formData
-
+
   fetchFeedForHomePage:(callback)->
-    options =
+    options =
       limit     : 6
       skip      : 0
       sort      :


default-apps: Removed dummy code.

default-apps: Removed dummy code.

--- client/landing/app/Applications/Viewer.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Viewer.kdapplication/AppController.coffee
@@ -35,8 +35,3 @@

   open:(path)->
     @getView().openPath path
-
-
-  openFile: (nodeView) ->
-    # TODO: Need to handle file types such as images.
-    @getView().openPath nodeView.path


Add .error callback back in

Add .error callback back in

--- common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
+++ common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
@@ -27,6 +27,7 @@
     else
       # if this post request fails, the error callback will catch it
       $.post(@ajax_url + cmd, data, callback)
+        .error => callback({success: false, error: "Error occured while performing this operation"})

   mock: (cmd, data) ->
     if cmd == 'is_student_calibrated'


changed font size/

changed font size/

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,7 +1,7 @@
 'editor':
   'showIndentGuide': true
   'fontFamily': 'Menlo'
-  'fontSize': 15
+  'fontSize': 13
 'core':
   'excludeVcsIgnoredPaths': true
   'projectHome': '~/Code'


Use fira code instead of fira mono

Use fira code instead of fira mono

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -74,7 +74,7 @@
     ]
     uriHandlerRegistration: "always"
   editor:
-    fontFamily: "Fira Mono"
+    fontFamily: "Fira Code"
     fontSize: 16
     invisibles: {}
     preferredLineLength: 120


atom config fixes

atom config fixes

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -9,8 +9,12 @@
   core:
     hideGitIgnoredFiles: true
     autoHideMenuBar: true
+    projectHome: "~/Code/"
   "exception-reporting":
     userId: "eb68b94c-a76b-64f4-46e7-bd087cf1c98f"
   welcome:
     showOnStartup: false
   linter: {}
+  "go-plus":
+    goPath: "~/Code/Go"
+    getMissingTools: false


change the atom config file to a sane default

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,35 +1,15 @@
+'release-notes':
+  'viewedVersion': '0.94.0'
+'welcome':
+  'showOnStartup': false
 'editor':
-  'showIndentGuide': true
-  'fontFamily': 'Menlo'
-  'fontSize': 15
+  'showIndentGuide': false
+  'fontFamily': 'Source Code Pro'
+  'fontSize': 12
 'core':
-  'excludeVcsIgnoredPaths': true
-  'ignoredNames': [
-    '.bundle'
-    '.git'
-    'log'
-    'repositories'
-    'tmp'
-    'vendor'
-  ]
-  'themes': [
-    'unity-ui'
-    'glacier-syntax'
-  ]
-  'hideGitIgnoredFiles': true
+  'audioBeep': false
   'disabledPackages': [
     'background-tips'
     'exception-reporting'
     'metrics'
   ]
-'spell-check':
-  'grammars': [
-    'text.plain'
-    'source.gfm'
-    'text.git-commit'
-  ]
-'welcome':
-  'showOnStartup': false
-'release-notes':
-  'viewedVersion': '0.89.0'
-'tree-view': {}


Fix the init.coffee workspaceView deprecation

--- atom.symlink/init.coffee
+++ atom.symlink/init.coffee
@@ -1,6 +1 @@
-path = require 'path'
-
-atom.workspaceView.eachEditorView (editorView) ->
-  editor = editorView.getEditor()
-  if path.extname(editor.getPath()) is '.md'
-    editor.setSoftWrap(true)
+# Add any auto-loaded Atom code on init here.


Util: Added empty() and any().

Util: Added empty() and any().


Track initial page loads for media with GA

--- src/scripts/modules/media/media.coffee
+++ src/scripts/modules/media/media.coffee
@@ -1,4 +1,5 @@
 define (require) ->
+  analytics = require('cs!helpers/handlers/analytics')
   Content = require('cs!models/content')
   BaseView = require('cs!helpers/backbone/views/base')
   MediaEndorsedView = require('cs!./endorsed/endorsed')
@@ -15,6 +16,9 @@
   return class MediaView extends BaseView
     template: template

+    regions:
+      media: '.media'
+
     initialize: (options) ->
       super()

@@ -24,11 +28,14 @@
       @uuid = options.uuid
       @model = new Content({id: @uuid, page: options.page})

+      @listenTo(@model, 'change:googleAnalytics', @trackAnalytics)
       @listenTo(@model, 'change:title', @updateTitle)
       @listenTo(@model, 'change:legacy_id change:legacy_version changePage', @updateLegacyLink)

-    regions:
-      media: '.media'
+    trackAnalytics: () ->
+      # Track loading using the media's own analytics ID, if specified
+      analyticsID = @model.get('googleAnalytics')
+      analytics.send(analyticsID) if analyticsID

     updateTitle: () ->
       @pageTitle = @model.get('title')


Added new line

Added new line

--- public/dreamcatcher/controllers/books_controller.coffee
+++ public/dreamcatcher/controllers/books_controller.coffee
@@ -53,7 +53,6 @@
   '.book .open .back click': (el) ->
     @showPage el, 'control'

-
   '.book .color-panel .swatches li click': (el) ->
     @changeBookColor el


fix parsing of little-endian exifs

fix parsing of little-endian exifs

https://www.media.mit.edu/pia/Research/deepview/exif.html#TiffHeader

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -303,8 +303,7 @@
       not exif or
       exif.byteLength < 14 or
       exif.getUint32(0) != 0x45786966 or
-      exif.getUint16(4) != 0 or
-      exif.getUint16(8) != 0x002A
+      exif.getUint16(4) != 0
     )
       return null

@@ -313,6 +312,9 @@
     else if exif.getUint16(6) == 0x4D4D
       little = false
     else
+      return null
+
+    if exif.getUint16(8, little) != 0x002A
       return null

     offset = 8 + exif.getUint32(10, little)


Remove debugging code

Remove debugging code

--- src/smc-hub/share/process-react.coffee
+++ src/smc-hub/share/process-react.coffee
@@ -94,9 +94,6 @@
     work = []
     visitor = (element, instance, context) ->
         if element.props?.auto_render_math?
-            console.log "======\nSETTING RENDERED MATHJAX\n======="
-            console.log "Visiting #{(item for item in element.props)} #{element.props.auto_render_math} #{element.type.displayName}"
-            console.log "Item has mathjax"
             if not element.props.auto_render_math
                 return false
             if element.type?.displayName == 'Misc-HTML' and element.props.value
@@ -108,7 +105,6 @@
         f = (html, cb) ->
             process html, viewer, (err, html2) ->
                 if not err
-                    console.log "SETTING RENDERED MATHJAX TO #{html2}"
                     set_rendered_mathjax(html, html2)
                 cb()
         async.map(work, f, cb)


make path public using API query

make path public using API query

--- src/smc-hub/test/api/text_files.coffee
+++ src/smc-hub/test/api/text_files.coffee
@@ -32,7 +32,7 @@
             body  :
                 project_id: project_id
                 content   : content
-                path      : 'A1/h1.txt'
+                path      : 'A1/doc1.txt'
             cb    : (err, resp) ->
                 expect(err).toEqual(null)
                 expect(resp?.event).toBe('file_written_to_project')
@@ -43,7 +43,7 @@
             event : 'read_text_file_from_project'
             body  :
                 project_id: project_id
-                path      : 'A1/h1.txt'
+                path      : 'A1/doc1.txt'
             cb    : (err, resp) ->
                 expect(err).toEqual(null)
                 expect(resp?.event).toBe('text_file_read_from_project')
@@ -55,7 +55,7 @@
             event : 'public_get_text_file'
             body  :
                 project_id: project_id
-                path      : 'A1/h1.txt'
+                path      : 'A1/doc1.txt'
             cb    : (err, resp) ->
                 expect(resp?.event).toBe('error')
                 expect(resp?.error).toInclude('is not public')
@@ -83,18 +83,25 @@
                 expect(resp?.error).toBe('not_public')
                 done(err)

-    it "uses the database to make a file public", (done) ->
-        api.db.user_query
-            account_id : api.account_id
-            query      : {public_paths:{project_id:project_id, path:'A1/h1.txt', description:'Handout #1'}}
-            cb         : done
+    it "uses API query to make a file public", (done) ->
+        api.call
+            event : 'query'
+            body  :
+                query  : {public_paths:{project_id:project_id, path:'A1/doc1.txt', description:'Handout #1'}}
+                #options: [{set:true}]
+            cb : (err, resp) ->
+                expect(resp?.error).toEqual(null)
+                expect(err).toEqual(null)
+                #expect(resp?.event).toBe('query')
+                expect(resp?.query).toIncludeKey('public_paths')
+                done(err)

     it "reads a public text file in a project", (done) ->
         api.call
             event : 'public_get_text_file'
             body  :
                 project_id: project_id
-                path      : 'A1/h1.txt'
+                path      : 'A1/doc1.txt'
             cb    : (err, resp) ->
                 expect(resp?.event).toBe('public_text_file_contents')
                 expect(resp?.data).toBe(content)


[automation-plugin]: fix jumping when clicking info icon on tables

--- plugins/automation/app/assets/javascripts/automation/node.coffee
+++ plugins/automation/app/assets/javascripts/automation/node.coffee
@@ -4,6 +4,9 @@
   $('[data-toggle="popover"][data-popover-type="table-info-hint"]').popover
     placement: 'top'
     trigger: 'focus'
+  $('[data-toggle="popover"][data-popover-type="table-info-hint"]').on 'click', (e) ->
+    e.stopPropagation()
+    e.preventDefault()

 @init_history_popover= () ->
   # init popovers elements


Added some missing event listeners to client.coffee.

--- src/client/client.coffee
+++ src/client/client.coffee
@@ -29,9 +29,12 @@
         document = @document
         propName = @API.nodes.propName
         server = @server
-        MouseEvents = ['click']
-        HTMLEvents = ['submit']
-        [MouseEvents, HTMLEvents].forEach (group) ->
+        MouseEvents = ['click', 'mousedown', 'mouseup', 'mouseover',
+                       'mousemove', 'mouseout']
+        HTMLEvents = ['submit', 'select', 'change', 'reset', 'focus', 'blur',
+                      'resize', 'scroll']
+        UIEvents = ['DOMFocusIn', 'DOMFocusOut', 'DOMActivate']
+        [MouseEvents, HTMLEvents, UIEvents].forEach (group) ->
             group.forEach (eventType) ->
                 document.addEventListener eventType, (event) ->
                     console.log "#{event.type} #{event.target[propName]}"


remove old test file

remove old test file

--- test/cookie-storage-test.coffee
+++ test/cookie-storage-test.coffee
@@ -1,90 +0,0 @@
-assert = require 'power-assert'
-{CookieStorage} = require '../dist/cookie-storage'
-
-describe 'CookieStorage', ->
-  beforeEach ->
-    @storage = new CookieStorage()
-    global.document = {}
-
-  afterEach ->
-    delete global.document
-
-  describe '#constructor', ->
-    it 'works', ->
-      @storage = new CookieStorage(path: '/')
-      @storage.setItem 'b', '2'
-      assert document.cookie is 'b=2;path=/'
-      @storage = new CookieStorage(domain: 'example.com')
-      @storage.setItem 'c', '3'
-      assert document.cookie is 'c=3;domain=example.com'
-      @storage = new CookieStorage(expires: new Date(1427005235068))
-      @storage.setItem 'd', '4'
-      assert document.cookie is 'd=4;expires=Sun, 22 Mar 2015 06:20:35 GMT'
-      @storage = new CookieStorage(secure: true)
-      @storage.setItem 'e', '5'
-      assert document.cookie is 'e=5;secure'
-
-  describe '#length', ->
-    it 'should be 0', ->
-      assert @storage.length is 0
-
-    it 'works', ->
-      document.cookie = 'a=1'
-      assert @storage.length is 1
-      document.cookie = 'a=1;b=2'
-      assert @storage.length is 2
-
-  describe '#key', ->
-    beforeEach ->
-      document.cookie = 'a=1;b=2'
-
-    it 'should be a function', ->
-      assert typeof @storage.key is 'function'
-
-    it 'works', ->
-      assert @storage.key(0) is 'a'
-      assert @storage.key(1) is 'b'
-
-  describe '#getItem', ->
-    beforeEach ->
-      document.cookie = 'a=1;b=2'
-
-    it 'should be a function', ->
-      assert typeof @storage.getItem is 'function'
-
-    it 'works', ->
-      assert @storage.getItem('a') is '1'
-      assert @storage.getItem('b') is '2'
-
-  describe '#setItem', ->
-    it 'should be a function', ->
-      assert typeof @storage.setItem is 'function'
-
-    it 'works', ->
-      @storage.setItem 'a', '1'
-      assert document.cookie is 'a=1'
-      @storage.setItem 'b', '2', path: '/'
-      assert document.cookie is 'b=2;path=/'
-      @storage.setItem 'c', '3', domain: 'example.com'
-      assert document.cookie is 'c=3;domain=example.com'
-      @storage.setItem 'd', '4', expires: new Date(1427005235068)
-      assert document.cookie is 'd=4;expires=Sun, 22 Mar 2015 06:20:35 GMT'
-      @storage.setItem 'e', '5', secure: true
-      assert document.cookie is 'e=5;secure'
-
-  describe '#removeItem', ->
-    it 'should be a function', ->
-      assert typeof @storage.removeItem is 'function'
-
-    it 'works', ->
-      @storage.removeItem 'a'
-      assert document.cookie is 'a=;expires=Thu, 01 Jan 1970 00:00:00 GMT'
-
-  describe '#clear', ->
-    it 'should be a function', ->
-      assert typeof @storage.clear is 'function'
-
-    it 'works', ->
-      document.cookie = 'a=1'
-      @storage.clear()
-      assert document.cookie is 'a=;expires=Thu, 01 Jan 1970 00:00:00 GMT'


Move util functions to the bottom near the rest

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -44,33 +44,6 @@

     document.addEventListener "webkitTransitionEnd", (e) =>
       @hideTransitionedPage e.target
-
-  # Private: Is the device touch enabled.
-  #
-  # Returns True if the device is touch enabled, else False.
-  isTouch: =>
-    if @os.android
-      !!('ontouchstart' of window)
-    else
-      window.Touch?
-
-  # Public: Is the device running Android
-  #
-  # Returns True if the device is running Android, else False.
-  isAndroid: =>
-    @os.android
-
-  # Public: Is the device running iOS
-  #
-  # Returns True if the device is running iOS, else False.
-  isIOS: =>
-    @os.ios
-
-  # Public: Get the version of the OS running on the device.
-  #
-  # Returns a String of the OS version.
-  osVersion: =>
-    @os.version.toString()

   # Public: Go to a specific page.
   #
@@ -309,6 +282,33 @@
     else
       false

+  # Private: Is the device touch enabled.
+  #
+  # Returns True if the device is touch enabled, else False.
+  isTouch: =>
+    if @os.android
+      !!('ontouchstart' of window)
+    else
+      window.Touch?
+
+  # Public: Is the device running Android
+  #
+  # Returns True if the device is running Android, else False.
+  isAndroid: =>
+    @os.android
+
+  # Public: Is the device running iOS
+  #
+  # Returns True if the device is running iOS, else False.
+  isIOS: =>
+    @os.ios
+
+  # Public: Get the version of the OS running on the device.
+  #
+  # Returns a String of the OS version.
+  osVersion: =>
+    @os.version.toString()
+
   handleEvents: (e) =>
     if @isTouch()
       switch e.type


Update follow symlinks setting description

--- src/config-schema.coffee
+++ src/config-schema.coffee
@@ -19,7 +19,8 @@
       followSymlinks:
         type: 'boolean'
         default: true
-        title: 'Follow symlinks when searching and scanning root directory'
+        title: 'Follow symlinks'
+        description: 'Used when searching and when opening files with the fuzzy finder.'
       disabledPackages:
         type: 'array'
         default: []


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -63,7 +63,7 @@
     else
       arcs.append("svg:text").attr("transform", (d, i) ->
         procent_val = Math.round(data[i].value/sum * 100)
-#        d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
+        #d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         d.outerRadius = radiuso
         "translate(" + arc.centroid(d) + ")")


Agent pie: handling null window.oldPieData on browser refresh

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -127,7 +127,7 @@

         # Create variable for old pie data that will be in scope within the path.transition attrTween function below
         # and handle null values that happen on browser refresh because onData() loads before ready()
-        window.oldPieData ? ready
+        window.oldPieData ? @ready
         lastPieData = window.oldPieData[@instanceDataId] ? d

         path = pathGroup.selectAll('path.pie').data(piedata)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -18,9 +18,9 @@
     mFontSize = 28
     width = 750 #width
     height = 450 #height
-    radiuso = 150 #outer radius
-    radiusi = 80 #inner radius
-    labelRadius = 180
+    radiuso = 200 #outer radius
+    radiusi = 100 #inner radius
+    labelRadius = 250

     color = d3.scale.ordinal()
       #.domain([1,10])


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -23,8 +23,8 @@
         radiuso = 130                           # Outer radius of the pie
         radiusi = 65                            # Inner radius of the pie (zero = pie, non-zero = donut)
         color = d3.scale.category20()           # Color scale for pie slices
-        dropshadowx = 3                         # X-offset for drop shadow filter
-        dropshadowy = 3                         # Y-offset for drop shadow filter
+        dropshadowx = 2                         # X-offset for drop shadow filter
+        dropshadowy = 2                         # Y-offset for drop shadow filter
         dropshadowblur = "1.2"                  # [STRING] Blur value for drop shadow filter

         pie = d3.layout.pie().value((d) -> d.value)


Agent pie: edit label transition

Agent pie: edit label transition


Maintenance: Do table related bindings only on table - not whold @el to get them destroyed on replaceing/re-render table.

--- app/assets/javascripts/app/controllers/_application_controller/table.coffee
+++ app/assets/javascripts/app/controllers/_application_controller/table.coffee
@@ -100,6 +100,7 @@

   events:
     'click .js-sort': 'sortByColumn'
+    'click .js-page': 'paginate'

   overviewAttributes: undefined
   #model:             App.TicketPriority,
@@ -339,6 +340,7 @@
     @sortList()
     bulkIds = @getBulkSelected()
     container = @renderTableContainer()
+    table = container.filter('.table')
     if !rows
       rows = @renderTableRows()
       @currentRows = clone(rows)
@@ -373,10 +375,10 @@

           if hit
             for event, callback of item.events
-              do (container, event, callback) ->
+              do (table, event, callback) ->
                 if cursorMap[event]
-                  container.find("tbody > tr > td:nth-child(#{position})").css('cursor', cursorMap[event])
-                container.on(event, "tbody > tr > td:nth-child(#{position})",
+                  table.find("tbody > tr > td:nth-child(#{position})").css('cursor', cursorMap[event])
+                table.on(event, "tbody > tr > td:nth-child(#{position})",
                   (e) ->
                     e.stopPropagation()
                     id = $(e.target).parents('tr').data('id')
@@ -387,10 +389,10 @@
     if !_.isEmpty(@bindRow)
       if @bindRow.events
         for event, callback of @bindRow.events
-          do (container, event, callback) ->
+          do (table, event, callback) ->
             if cursorMap[event]
-              container.find('tbody > tr').css( 'cursor', cursorMap[event] )
-            container.on(event, 'tbody > tr',
+              table.find('tbody > tr').css( 'cursor', cursorMap[event] )
+            table.on(event, 'tbody > tr',
               (e) ->
                 id = $(e.target).parents('tr').data('id')
                 callback(id, e)
@@ -400,8 +402,8 @@
     if @bindCheckbox
       if @bindCheckbox.events
         for event, callback of @bindCheckbox.events
-          do (container, event, callback) ->
-            container.delegate('input[name="bulk"]', event, (e) ->
+          do (table, event, callback) ->
+            table.delegate('input[name="bulk"]', event, (e) ->
               e.stopPropagation()
               id      = $(e.currentTarget).parents('tr').data('id')
               checked = $(e.currentTarget).prop('checked')
@@ -412,19 +414,19 @@
     if @tableId

       # enable resize column
-      container.on('mousedown', '.js-col-resize', @onColResizeMousedown)
-      container.on('click', '.js-col-resize', @stopPropagation)
+      table.on('mousedown', '.js-col-resize', @onColResizeMousedown)
+      table.on('click', '.js-col-resize', @stopPropagation)

     # enable checkbox bulk selection
     if @checkbox

       # click first tr>td, catch click
-      container.delegate('tr > td:nth-child(1)', 'click', (e) ->
+      table.delegate('tr > td:nth-child(1)', 'click', (e) ->
         e.stopPropagation()
       )

       # bind on full bulk click
-      container.delegate('input[name="bulk_all"]', 'change', (e) =>
+      table.delegate('input[name="bulk_all"]', 'change', (e) =>
         e.stopPropagation()
         clicks = []
         if $(e.currentTarget).prop('checked')
@@ -473,21 +475,7 @@
             $(@).width( originals.eq(index).outerWidth() )
           return helper
         update: @dndCallback
-      container.find('tbody').sortable(dndOptions)
-
-    # click on pager
-    container.delegate('.js-page', 'click', (e) =>
-      e.stopPropagation()
-      page = $(e.currentTarget).attr 'data-page'
-      if @pagerAjax
-        @navigate "#{@pagerBaseUrl}#{(parseInt(page) + 1)}"
-      else
-        render = =>
-          @shownPage = page
-          @renderTableFull()
-        App.QueueManager.add('tableRender', render)
-        App.QueueManager.run('tableRender')
-    )
+      table.find('tbody').sortable(dndOptions)

     @el.html(container)
     @setBulkSelected(bulkIds)
@@ -673,6 +661,18 @@
     page = parseInt(page)
     @objects.slice(page * @shownPerPage, (page + 1) * @shownPerPage)

+  paginate: (e) =>
+    e.stopPropagation()
+    page = $(e.currentTarget).attr('data-page')
+    if @pagerAjax
+      @navigate "#{@pagerBaseUrl}#{(parseInt(page) + 1)}"
+    else
+      render = =>
+        @shownPage = page
+        @renderTableFull()
+      App.QueueManager.add('tableRender', render)
+      App.QueueManager.run('tableRender')
+
   sortList: =>
     return if _.isEmpty(@objects)

@@ -836,7 +836,6 @@
     id = $(e.currentTarget).parents('tr').data('id')
     name = e.currentTarget.getAttribute('data-table-action')
     @runAction(name, id)
-    console.log 'onActionButtonClicked'

   runAction: (name, id) =>
     action = _.findWhere @actions, name: name


teams: invitations new logic/design applied

--- client/admin/lib/views/invitations/admininvitationsview.coffee
+++ client/admin/lib/views/invitations/admininvitationsview.coffee
@@ -4,11 +4,16 @@
 KDButtonView            = kd.ButtonView
 KDTabPaneView           = kd.TabPaneView
 InviteSomeoneView       = require './invitesomeoneview'
+AdminSubTabHandleView   = require './../adminsubtabhandleview'
 PendingInvitationsView  = require './pendinginvitationsview'
 AcceptedInvitationsView = require './acceptedinvitationsview'

+module.exports = class AdminInvitationsView extends KDView

-module.exports = class AdminInvitationsView extends KDView
+  PANE_NAMES_BY_ROUTE =
+    'Invite'   : 'Invite Teammates'
+    'Pending'  : 'Pending Invitations'
+    'Accepted' : 'Accepted Invitations'

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

@@ -17,7 +22,6 @@
     super options, data

     @createTabView()
-    @createInviteButton()

   createTabView: ->
@@ -27,30 +31,24 @@
     @addSubView tabView = @tabView = new KDTabView
       hideHandleCloseIcons : yes
       maxHandleWidth       : 210
+      tabHandleClass       : AdminSubTabHandleView

-    tabView.addPane pending  = new KDTabPaneView name: 'Pending Invitations'
-    tabView.addPane accepted = new KDTabPaneView name: 'Accepted Invitations'
-    tabView.addPane invite   = new KDTabPaneView name: 'Invite', hiddenHandle: yes
+    tabView.addPane invite   = new KDTabPaneView
+      name         : PANE_NAMES_BY_ROUTE.Invite
+      route        : '/Admin/Invitations/Invite'
+    tabView.addPane pending  = new KDTabPaneView
+      name         : PANE_NAMES_BY_ROUTE.Pending
+      route        : '/Admin/Invitations/Pending'
+    tabView.addPane accepted = new KDTabPaneView
+      name         : PANE_NAMES_BY_ROUTE.Accepted
+      route        : '/Admin/Invitations/Accepted'

     pending.addSubView  pendingView  = new PendingInvitationsView  {}, data
     accepted.addSubView acceptedView = new AcceptedInvitationsView {}, data
-    invite.addSubView   inviteView   = new InviteSomeoneView {}, data
+    invite.addSubView   inviteView   = new InviteSomeoneView       {}, data

     tabView.showPaneByIndex 0

-    inviteView.on 'InvitationViewCancelled', =>
-      tabView.showPaneByIndex tabView.lastOpenPaneIndex
-      @inviteButton.show()
-
-    tabView.on 'PaneDidShow', => @inviteButton.show()
     inviteView.on 'NewInvitationsAdded', -> pendingView.refresh()

-
-  createInviteButton: ->
-
-    @addSubView @inviteButton = new KDButtonView
-      title    : 'INVITE SOMEONE'
-      cssClass : 'solid compact green invite'
-      callback : =>
-        @tabView.showPaneByName 'Invite'
-        @inviteButton.hide()
+    @on 'SubTabRequested', (action, identifier) -> tabView.showPaneByName PANE_NAMES_BY_ROUTE[action]


Parallelized join and filter

Parallelized join and filter

--- list.coffee
+++ list.coffee
@@ -71,7 +71,7 @@

     filter: (f = _.identity) ->
         # Reactive
-        J.List _.filter @getValues(), f
+        J.List _.filter @map().getValues(), f

     forEach: (f) ->
         # Reactive
@@ -101,6 +101,7 @@
                         @get i
                     else
                         lst.get i - @size()
+                true
             )
         else
             J.List @getValues().concat lst.getValues()
@@ -124,7 +125,7 @@

     join: (separator) ->
         # Reactive
-        @getValues().join separator
+        @map().getValues().join separator

     indexOf: (x, equalsFunc = J.util.equals) ->
         for i in [0...@size()]
@@ -132,7 +133,7 @@
             return i if equalsFunc y, x
         -1

-    lazyMap: (f) ->
+    lazyMap: (f = _.identity) ->
         # Reactive
         if Tracker.active
             J.AutoList(
@@ -143,15 +144,18 @@
         else
             J.List @getValues().map f

-    map: (f) ->
+    map: (f = _.identity) ->
         # Reactive
         # Enables parallel fetching
         if Tracker.active
-            J.AutoList(
-                => @size()
-                (i) => f @get(i), i
-                true # This makes it not lazy
-            )
+            if f is _.identity and @ instanceof J.AutoList and @onChange
+                @
+            else
+                J.AutoList(
+                    => @size()
+                    (i) => f @get(i), i
+                    true # This makes it not lazy
+                )
         else
             J.List @getValues().map f


adding more comment tests -- this terminator/comment as-block business really seems to have opened them up a bit

--- test/test_funky_comments.coffee
+++ test/test_funky_comments.coffee
@@ -33,3 +33,15 @@
   two: 2
     # comment
 }
+
+result: if true # comment
+  false
+
+ok not result
+
+result: if false
+  false
+else # comment
+  45
+
+ok result is 45


Add tests for colorize()

Add tests for colorize()

--- test/colorize_test.coffee
+++ test/colorize_test.coffee
@@ -2,7 +2,7 @@

 { colorize, colorizeToArray } = require "../#{process.env.JSLIB or 'lib'}/colorize"

-describe 'colorize', ->
+describe 'colorizeToArray', ->

   it "should return ' <value>' for a scalar value", ->
     assert.deepEqual [' 42'], colorizeToArray(42)
@@ -27,3 +27,13 @@

   it "should return '-<deleted item>' for an array diff", ->
     assert.deepEqual [' [', '   10', '-  20', '   30', ' ]'], colorizeToArray([[' ', 10], ['-', 20], [' ', 30]])
+
+
+describe 'colorize', ->
+
+  it "should return a string with ANSI escapes", ->
+    assert.equal colorize({ foo: { __old: 42, __new: 10 } }), " {\n\u001b[31m-  foo: 42\u001b[39m\n\u001b[32m+  foo: 10\u001b[39m\n }\n"
+
+  it "should return a string without ANSI escapes on { color: false }", ->
+    assert.equal colorize({ foo: { __old: 42, __new: 10 } }, color: no), " {\n-  foo: 42\n+  foo: 10\n }\n"
+


Update test for parse debugging

Update test for parse debugging

--- test/document_test.coffee
+++ test/document_test.coffee
@@ -175,27 +175,27 @@
           document.toString(),
           "(DOCUMENT (sentence (word4) (word3) (word3)))")

-  describe "::debugParse(callback)", ->
+  describe "::setDebug(callback)", ->
     debugCalls = null

     beforeEach ->
       debugCalls = []
       document.setLanguage(language)
-      document.debugParse (msg, params) ->
+      document.setDebug (msg, params) ->
         debugCalls.push([msg, params])

     it "calls the given callback for each parse event", ->
       document.setInputString("first-word second-word")

       assert.deepEqual([
-        ["reduce", { sym: "sentence", count: "1" }]
-        ["shift", { state: "1" }],
-        ["accept", {}]
+        ["reduce",{"sym":"sentence","count":"1"}],
+        ["shift",{"state":"1"}],
+        ["accept",{}]
       ], debugCalls.slice(-3))

     describe "when given a falsy value", ->
       beforeEach ->
-        document.debugParse(false)
+        document.setDebug(false)

       it "disables debugging", ->
         document.setInputString("first-word second-word")
@@ -204,38 +204,7 @@
     describe "when given a truthy value that isn't a function", ->
       it "raises an exception", ->
         assert.throws((->
-          document.debugParse("5")
-        ), /Debug callback must .* function .* falsy/)
-
-  describe "::debugLex(callback)", ->
-    debugCalls = null
-
-    beforeEach ->
-      debugCalls = []
-      document.setLanguage(language)
-      document.debugLex (msg, params) ->
-        debugCalls.push([msg, params])
-
-    it "calls the given callback for each lex event", ->
-      document.setInputString("first-word second-word")
-
-      assert.deepEqual([
-        ['lookahead', { char: '0' }],
-        ['accept_token', { sym: 'end' }]
-      ], debugCalls.slice(-2))
-
-    describe "when given a falsy value", ->
-      beforeEach ->
-        document.debugLex(false)
-
-      it "disables debugging", ->
-        document.setInputString("first-word second-word")
-        assert.equal(0, debugCalls.length)
-
-    describe "when given a truthy value that isn't a function", ->
-      it "raises an exception", ->
-        assert.throws((->
-          document.debugLex("5")
+          document.setDebug("5")
         ), /Debug callback must .* function .* falsy/)

   describe "treating a document as an AST node", ->


No need to reverse comments, they are coming in correct order

--- client/app/Applications/Inbox.kdapplication/Views/InboxMessageDetail.coffee
+++ client/app/Applications/Inbox.kdapplication/Views/InboxMessageDetail.coffee
@@ -47,7 +47,6 @@
   fetchComments:(callback)->
     pm = @getData()
     pm.commentsByRange to: 3, (err, comments)->
-      comments.reverse() # temporary solution until we refactor InboxMessageThreadView - 07/2012 Sinan
       callback err, comments

   enhanceBody:(body)->


Dropbox: Handled os kite error on upload process.

--- client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -821,9 +821,14 @@
         duration   : 120000

       kiteController.run "mkdir -p Web ; zip -r #{relativePath} #{plainPath}", (err, res) =>
-        return  warn err if err
-        notification.notificationSetTitle "Uploading zipped file..."
-        kallback()
+        if err
+          message = if err.name is "ExitError" then "An error occured. It seems zip is not installed on your VM."
+          else "An error occured, please try again."
+          notification.notificationSetTitle message
+          notification.notificationSetTimer 4000
+          notification.setClass "error"
+        else
+          kallback()
     else
       notification = new KDNotificationView
         title      : "Uploading your file..."


tweak search

tweak search

--- public/javascripts/2013/teams.coffee
+++ public/javascripts/2013/teams.coffee
@@ -14,6 +14,9 @@
         speed: 50
         finished: (opts) -> opts.loading.msg.hide()
         finishedMsg: 'No more teams. :('
+
+    $(".header-box .search input").blur (e) =>
+      $("form.search").submit()

     # re-send invites
     $(this).delegate '.invites a', 'click', (e) ->


setTimeout on recreating websocket. Related to #39

--- client/lib/callApi.coffee
+++ client/lib/callApi.coffee
@@ -15,7 +15,8 @@
             ws = new WebSocket(WS_API_URL + endpoint)
             ws.onmessage = (msg) ->
                 cb msg.data
-            ws.onclose = wsCloseListener
+            ws.onclose = ->
+                setTimeout wsCloseListener, 20 * 1000
             timeout = setInterval ->
                   if not hasMementoKey(endpoint)
                       clearInterval(timeout)


Center viewport position

Center viewport position

--- client/publication.coffee
+++ client/publication.coffee
@@ -219,7 +219,7 @@
 Template.publicationScroller.events
   'click': (e, template) ->
     $target = $(e.target)
-    offset = if $target.is('#scroller') then e.offsetY else $target.offset().top - $target.parent().offset().top
+    offset = if $target.is('#scroller') then e.offsetY - $('#scroller .viewport').height() / 2 else $target.offset().top - $target.parent().offset().top
     scrollToOffset offset

 Template.publicationAnnotations.annotations = ->


this test fails all the time in jenkins, probably because it's crazy async

Let's find out.

TEST PLAN:
  1) Less Fragile Failures

Change-Id: I4e0f7137850f107ae3e758c6d0dee5206979123c
Reviewed-on: https://gerrit.instructure.com/52021
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Tested-by: Jenkins
Product-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>
QA-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>

--- spec/coffeescripts/react_files/components/FilePreviewSpec.coffee
+++ spec/coffeescripts/react_files/components/FilePreviewSpec.coffee
@@ -90,17 +90,26 @@

-  test 'clicking the info button should render out the info panel', ->
+  asyncTest 'clicking the info button should render out the info panel', ->
+    expect(1)
     @runRouter ->
       $('.ef-file-preview-header-info').click()
-      ok $('.ef-file-preview-information-container').length, 'The info panel did not show'
+      setTimeout ()->
+        start()
+        ok $('.ef-file-preview-information-container').length, 'The info panel did not show'

-  test 'clicking the info button after the panel has been opened should hide the info panel', ->
+  asyncTest 'clicking the info button after the panel has been opened should hide the info panel', ->
+    expect(2)
     @runRouter ->
       $('.ef-file-preview-header-info').click()
-      ok $('.ef-file-preview-information-container').length, 'The info panel did not show'
-      $('.ef-file-preview-header-info').click()
-      ok !$('.ef-file-preview-information-container').length, 'The info panel did not close'
+      setTimeout ()->
+        start()
+        ok $('.ef-file-preview-information-container').length, 'The info panel did not show'
+        stop()
+        $('.ef-file-preview-header-info').click()
+        setTimeout ()->
+          start()
+          ok !$('.ef-file-preview-information-container').length, 'The info panel did not close'

   test 'opening the preview for one file should show navigation buttons for the previous and next files in the current folder', ->
     @runRouter ->


Binding connections now are connecting in runtime if needed

--- impl/base/level1/binding.coffee
+++ impl/base/level1/binding.coffee
@@ -80,6 +80,11 @@
 				val = @child.getValue()
 			else
 				val = Binding.getItemById @binding, @itemId
+
+			if oldVal and not @isConnected
+				@connect()
+				oldVal = null
+
 			if oldVal isnt val
 				@disconnect()
 				@item = val


Remove console.log debugging output

Remove console.log debugging output

--- src/components/course-administration/period-roster.cjsx
+++ src/components/course-administration/period-roster.cjsx
@@ -26,8 +26,6 @@

   render: ->
-    console.log "Rendering for course #{@props.courseId} / #{@props.period.id}"
-    console.log RosterStore.getStudentsForPeriod(@props.courseId, @props.period.id)

Period: {@props.period.name}

       <BS.Row>


update roster specs to enzyme

update roster specs to enzyme

--- tutor/specs/components/course_settings/roster.spec.coffee
+++ tutor/specs/components/course_settings/roster.spec.coffee
@@ -1,4 +1,4 @@
-{Testing, sinon, _, ReactTestUtils} = require '../helpers/component-testing'
+{React, Testing, pause, sinon, _, ReactTestUtils} = require '../helpers/component-testing'
 ld = require 'lodash'
 Roster = require '../../../src/components/course-settings/roster'

@@ -32,58 +32,51 @@
     PeriodActions.restore.restore()

   it 'renders period panels', ->
-    Testing.renderComponent( Roster, props: @props ).then ({dom}) ->
-      titles = _.pluck(dom.querySelectorAll('.nav-tabs li a'), 'textContent')
-      expect(titles)
-        .to.deep.equal(['1st', '2nd', '3rd', '5th', '6th', '10th'])
-
+    wrapper = mount(<Roster {...@props} />)
+    for period, i in ['1st', '2nd', '3rd', '5th', '6th', '10th']
+      expect(wrapper.find('.periods .nav-tabs li').at(i).text())
+        .to.equal(period)
+    undefined

   it 'renders students in the panels', ->
-    Testing.renderComponent( Roster, props: @props ).then ({dom}) ->
-      names = _.pluck(dom.querySelectorAll("table.roster tr td:nth-child(2)"), 'textContent')
-      expect(names).to.deep.equal(
-        ['Angstrom', 'Glass', 'Hackett', 'Jaskolski', 'Lowe', 'Tromp', 'Reilly']
-      )
+    wrapper = mount(<Roster {...@props} />)
+    for name, i in ['Angstrom', 'Glass', 'Hackett', 'Jaskolski', 'Lowe', 'Tromp', 'Reilly']
+      expect(wrapper.find('.roster tbody tr').at(i).find('td').at(1).text())
+        .to.equal(name)
+    undefined

-  it 'switches roster when tab is clicked', (done) ->
-    Testing.renderComponent( Roster, props: @props ).then (result) ->
-      Testing.actions.click(result.dom.querySelector('.periods .nav-tabs li:nth-child(2) a'))
-      _.defer ->
-        names = _.pluck(result.dom.querySelectorAll("table.roster tr td:nth-child(2)"), 'textContent')
-        expect(names).to.deep.equal(
-          ['Bloom', 'Kirlin']
-        )
-        done()
-    true
+  it 'switches roster when tab is clicked', ->
+    wrapper = mount(<Roster {...@props} />)
+    tab = wrapper.find('.periods .nav-tabs li').at(1).find('a')
+    tab.simulate('click')
+    expect(wrapper.find('.roster tbody tr').at(0).find('td').at(1).text())
+      .to.equal('Bloom')
+    expect(wrapper.find('.roster tbody tr').at(1).find('td').at(1).text())
+      .to.equal('Kirlin')
+    undefined

   ## this is flaky, doesn't always complete in time
-  xit 'can archive periods', (done) ->
-    Testing.renderComponent( Roster, props: @props, unmountAfter: 30 ).then ({dom}) ->
-      Testing.actions.click(dom.querySelector('.control.archive-period'))
-      expect(dom.querySelector('.nav-tabs .active').textContent).to.equal('1st')
-      expect(dom.querySelector('.roster tbody td').textContent).to.equal('Rabbit')
-      _.defer ->
-        Testing.actions.click(document.querySelector('button.archive-section'))
-        expect(PeriodActions.delete).to.have.been.called
-        _.defer ->
-          expect(dom.querySelector('.nav-tabs .active').textContent).to.equal('2nd')
-          expect(dom.querySelector('.roster tbody td').textContent).to.equal('Molly')
-          done()
-    true
+  it 'can archive periods', ->
+    wrapper = mount(<Roster {...@props} />)
+    expect(wrapper.find('.nav-tabs .active').text()).to.equal('1st')
+    wrapper.find('.control.archive-period').simulate('click')
+    Testing.actions.click(document.querySelector('button.archive-section'))
+    expect(PeriodActions.delete).to.have.been.called
+    wrapper.update()
+    expect(wrapper.find('.nav-tabs .active').text()).to.equal('2nd')
+    expect(wrapper.find('.roster tbody tr').at(0).find('td').at(1).text())
+      .to.equal('Bloom')
+    undefined

-  it 'can view and unarchive periods', (done) ->
-    Testing.renderComponent( Roster, props: @props ).then ({dom}) ->
-      Testing.actions.click(dom.querySelector('.view-archived-periods > button'))
-      _.defer ->
-        periods = _.pluck(document.querySelectorAll(
-          '.view-archived-periods-modal tbody td:first-child'), 'textContent'
-        )
-        expect(periods).to.deep.equal(
-          ['4th', '7th']
-        )
-        Testing.actions.click(
-          document.querySelector('.view-archived-periods-modal .restore-period button')
-        )
-        expect(PeriodActions.restore).to.have.been.called
-        done()
-    true
+  it 'can view and unarchive periods', ->
+    wrapper = mount(<Roster {...@props} />)
+    wrapper.find('.view-archived-periods button').simulate('click')
+    periods = _.pluck(document.querySelectorAll(
+      '.view-archived-periods-modal tbody td:first-child'), 'textContent'
+    )
+    expect(periods).to.deep.equal(['4th', '7th'])
+    Testing.actions.click(
+      document.querySelector('.view-archived-periods-modal .restore-period button')
+    )
+    expect(PeriodActions.restore).to.have.been.called
+    undefined


Pass through everything to controller.link

--- client/packages.coffee
+++ client/packages.coffee
@@ -65,7 +65,7 @@
 										controllers = [controllers]

 									for controller in controllers
-										controller.link? scope, element, attrs
+										controller.link? arguments...

 								link? arguments...


well it kind of works

well it kind of works

--- client/specs/go.coffee
+++ client/specs/go.coffee
@@ -8,7 +8,9 @@
     ESCAPECHAR: "\\"
     QUOTATION_MARK1: "\""
     QUOTATION_MARK2: "\'"
+    QUOTATION_MARK3: "\`"
     LINE_COMMENT: "//"
+    BLOCK_COMMENT: ["/*", "*/"]
     PAIRS1: "()"
     PAIRS2: "[]"
     PAIRS3: "{}"
@@ -22,7 +24,8 @@
     KEY2: [
         "uint8", "uint16", "uint32", "uint64", "int8", "int16",
         "int32", "int64", "float32", "float64", "complex64",
-        "complex128", "byte", "rune"
+        "complex128", "byte", "rune",
+        "string", "int"
     ]
     KEY3: [
         "true", "false", "iota", "nil",


fix(draft-list): Nesting of participants class name causing problems

--- internal_packages/inbox-contact-elements/lib/Participants.cjsx
+++ internal_packages/inbox-contact-elements/lib/Participants.cjsx
@@ -20,9 +20,9 @@
     chips = @getParticipants().map (p) =>
       <ContactChip key={p.nameEmail()} clickable={@props.clickable} participant={p} />

-

+
       {chips}
-

+

   getParticipants: =>
     myEmail = NamespaceStore.current().emailAddress


Remove duplicate 'height' key

Remove duplicate 'height' key

--- coffee/views/start.coffee
+++ coffee/views/start.coffee
@@ -27,7 +27,6 @@
   btnAlert = Ti.UI.createButton
     height: 60
     width: 'auto'
-    height: 'auto'
     title: 'Greet me!'
     font: {fontSize: 24, fontWeight: 'bold', fontColor: '#000'}
     color: '#000'


no idea why this was commented

no idea why this was commented

--- vendor/assets/javascripts/overlayme/overlays/image.coffee
+++ vendor/assets/javascripts/overlayme/overlays/image.coffee
@@ -26,8 +26,8 @@

   checkbox: ->
     @checkbox = @make 'input', { type: "checkbox" }
-    # if $(@image.el).css('visibility') == 'visible'
-    #   @checkbox.checked = true
+    if $(@image.el).css('visibility') == 'visible'
+      @checkbox.checked = true
     $(@checkbox).bind 'change', (event) =>
       @flickVisibility()
     @checkbox


US5053 - Fixed bug preventing the self rights from being added until a regular right was added.

--- sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
@@ -12,7 +12,21 @@

   populateTable(roles)

+  $("#addSelfRightUi button").click ->
+    console.log("Clicking the self button")
+    option = $('#addSelfRightUi option:selected')
+    if (option.val() == 'none')
+      return
+    text = option.text()
+    right = createLabel('right', text)
+    right = wrapInputWithDeleteButton(right, "span", text)
+    $("#addSelfRightUi").parent().append(right)
+    $("#addSelfRightUi").parent().append(" ")
+    populateRightComboBox($(@).parents("tr"))
+    enableSaveButtonIfPossible($(@).parents("tr"))
+
   $("#addRightUi button").click ->
+    console.log("The regular button clicked")
     option = $('#addRightUi option:selected')
     if (option.val() == 'none')
       return
@@ -23,6 +37,7 @@
     $("#addRightUi").parent().append(" ")
     populateRightComboBox($(@).parents("tr"))
     enableSaveButtonIfPossible($(@).parents("tr"))
+

   #Wire up Add Role button
   $("#addGroupButton").click ->
@@ -126,17 +141,17 @@
     if ($(@).val() != "none")
       $(@).remove()

-#  $("#addSelfRightUi option").each ->
-#    if ($(@).val() != "none")
-#      $(@).remove()
+  $("#addSelfRightUi option").each ->
+    if ($(@).val() != "none")
+      $(@).remove()

   for right in defaultRights
     if (curRights.indexOf(right) < 0)
       $("#addRightUi select").append($("<option></option>").val(right).text(right))

-#  for right in selfRights
-#    if (curSelfRights.indexOf(right) < 0)
-#      $("#addSelfRightUi select").append($("<option></option>").val(right).text(right))
+  for right in selfRights
+    if (curSelfRights.indexOf(right) < 0)
+      $("#addSelfRightUi select").append($("<option></option>").val(right).text(right))

 wrapInputWithDeleteButton = (input, type, name) ->
   div = $('<span>').addClass("input-append")
@@ -214,11 +229,10 @@
     return rights

 getSelfRights = (tr) ->
-#    rights = []
-#    tr.find(SELF_RIGHT_COL).find(".customLabel").each ->
-#      rights.push($(@).text())
-#    return rights
-  return null
+    rights = []
+    tr.find(SELF_RIGHT_COL).find(".customLabel").each ->
+      rights.push($(@).text())
+    return rights

 getRoles = (tr) ->
     roles = []


remove cid from mediatheque

remove cid from mediatheque

--- BackofficeBundle/Resources/public/coffee/mediatheque/GalleryCollectionView.coffee
+++ BackofficeBundle/Resources/public/coffee/mediatheque/GalleryCollectionView.coffee
@@ -31,7 +31,7 @@
   addElementToView: (mediaData) ->
     mediaModel = new GalleryModel
     mediaModel.set mediaData
-    new GalleryView($.extend({}, options,
+    new GalleryView($.extend({}, @options,
       media: mediaModel
       domContainer: this.$el.find('.superbox'))
     )


now we have to explicitly disable the archive and rss feed. yay deep merge

--- config/application.coffee
+++ config/application.coffee
@@ -30,6 +30,8 @@
       paths:
         pages: "app/pages/**/*.md"
         index: "index.html"
+        archive: null
+        rss: null
       lib:
         Category: require('../lib/category')


Correct import/export test

Correct import/export test

--- tests/tests/export.coffee
+++ tests/tests/export.coffee
@@ -48,7 +48,7 @@
                 - Line 2.3.1
             - Line 3""", 'text/plain'
 t.sendKey 'down'
-t.sendKeys '3<'
+t.sendKeys ['3', 'shift+tab']
 t.sendKey 'up'
 t.sendKeys 'dd'
 t.expectExport 'application/json',
@@ -105,7 +105,7 @@
   ]
 }""", 'application/json'
 t.sendKey 'down'
-t.sendKeys '3<'
+t.sendKeys ['3', 'shift+tab']
 t.sendKey 'up'
 t.sendKeys 'dd'
 t.expectExport 'application/json',
@@ -132,7 +132,7 @@
             - [COMPLETE] Line 2
             - [COMPLETE] Line 3""", "text/plain"
 t.sendKey 'down'
-t.sendKeys '3<'
+t.sendKeys ['3', 'shift+tab']
 t.sendKey 'up'
 t.sendKeys 'dd'
 t.expectExport 'application/json',


Use ExerciseIdentifierLink

Use ExerciseIdentifierLink

--- src/components/exercise/group.cjsx
+++ src/components/exercise/group.cjsx
@@ -4,7 +4,7 @@
 camelCase = require 'camelcase'

 ChapterSectionMixin = require '../chapter-section-mixin'
-
+ExerciseIdentifierLink = require './identifier-link'
 DEFAULT_GROUP =
   show: false

@@ -61,10 +61,7 @@
       ]

     if exercise_uid
-      exerciseUid =
-        {exercise_uid}
-
-      groupDOM.push(exerciseUid)
+      groupDOM.push <ExerciseIdentifierLink key='exercise-uid' exerciseId={exercise_uid} />

       {groupDOM}


omit double 'comes with' related content for mpqs

--- src/components/exercise/index.cjsx
+++ src/components/exercise/index.cjsx
@@ -103,14 +103,16 @@

     <ExFooter {...canContinueControlProps} {...footerProps} panel='review'/>

-  renderIdLink: ->
+  renderIdLink: (related = true) ->
     {parts} = @props
     step = _.last(parts)
+
+    related_content = step.related_content if related

     if step.content?.uid
       <ExerciseIdentifierLink key='exercise-uid'
         exerciseId={step.content?.uid}
-        related_content={step.related_content}/>
+        related_content={related_content}/>

 ExerciseWithScroll = React.createClass
@@ -160,7 +162,7 @@
       <ExerciseBadges isMultipart={true}/>
       {exerciseGroup}
       {exercisePartsWithScroll}
-      {@renderIdLink()}
+      {@renderIdLink(false)}
     </CardBody>

@@ -184,7 +186,7 @@
       <ExerciseBadges isMultipart={true}/>
       {exerciseGroup}
       {exerciseParts}
-      {@renderIdLink()}
+      {@renderIdLink(false)}
     </CardBody>

 module.exports = {Exercise, ExerciseWithScroll}


Updated virtual test

Updated virtual test

--- test/generators/relational/has_one.coffee
+++ test/generators/relational/has_one.coffee
@@ -466,7 +466,8 @@

       Owner.findOne (err, owner) ->
         assert.ok(!err, "No errors: #{err}")
-        assert.ok(owner, 'Reverse found model')
+        assert.ok(owner, 'Owners found')
+        flat_id = owner.get('flat').id

         json = owner.toJSON()
         assert.ok(json.hasOwnProperty(json_key), 'Serialized flat')
@@ -477,16 +478,21 @@
         virtual_json = owner.toJSON()
         assert.ok(!virtual_json.hasOwnProperty(json_key), 'Did not serialize flat')

-        flat = owner.get('flat')
-        flat_id = flat.id
-        owner.save {flat: null}, bbCallback (err) ->
-          assert.ok(!err, "No errors: #{err}")
-
-          require('../../../lib/cache').reset() # reset cache
-          Owner.find owner.id, (err, loaded_owner) ->
-            assert.ok(!err, "No errors: #{err}")
-            assert.equal(loaded_owner.get('flat').id, flat_id, 'Virtual flat is not saved')
-            done()
+        owner_with_flat = new Owner(json)
+        assert.equal(owner_with_flat.get('flat').id, flat_id, 'Virtual with flat was deserialized')
+
+        owner_with_virtual_flat = new Owner(virtual_json)
+        assert.equal(owner_with_virtual_flat.get('flat'), null, 'Virtual without flat was deserialized')
+        done()
+
+        # owner.save {flat: null}, bbCallback (err) ->
+        #   assert.ok(!err, "No errors: #{err}")
+
+        #   require('../../../lib/cache').reset() # reset cache
+        #   Owner.find owner.id, (err, loaded_owner) ->
+        #     assert.ok(!err, "No errors: #{err}")
+        #     assert.equal(loaded_owner.get('flat').id, flat_id, 'Virtual flat is not saved')
+        #     done()

 # each model should have available attribute 'id', 'name', 'created_at', 'updated_at', etc....


whether solution should show should also check whether or not answer correctness is in props

--- src/components/question/index.cjsx
+++ src/components/question/index.cjsx
@@ -43,11 +43,17 @@
   getChildContext: ->
     processHtmlAndMath: @props.processHtmlAndMath

+  hasAnswerCorrectness: ->
+    {correct_answer_id, model} = @props
+    {answers} = model
+
+    correct_answer_id? or not _.isEmpty _.pluck(answers, 'correctness')
+
   hasSolution: ->
-    {model} = @props
+    {model, correct_answer_id} = @props
     {collaborator_solutions} = model

-    not _.isEmpty(_.pluck(collaborator_solutions, 'content_html'))
+    @hasAnswerCorrectness() and not _.isEmpty(_.pluck(collaborator_solutions, 'content_html'))

   render: ->
     {model, correct_answer_id, exercise_uid, className} = @props


add proxy config to karma

add proxy config to karma

--- config/grunt/karma.coffee
+++ config/grunt/karma.coffee
@@ -18,6 +18,8 @@
   grunt.config('karma', _.extend(remoteKarma,
     options:
       configFile: 'config/karma.js'
+      proxies:
+        '/local': "http://localhost:#{grunt.config('port')}"
       files: [
         'node_modules/jquery/dist/jquery.js'
         'node_modules/lodash/lodash.js'


Campaign: increase campaign multiplier by 4

--- workers/social/lib/social/models/referral/campaign.coffee
+++ workers/social/lib/social/models/referral/campaign.coffee
@@ -136,4 +136,5 @@

    increaseGivenAmountSpace:(size, callback)->
     [size, callback] = [@campaignPerEventAmount, size] unless callback
+    size = size * 4
     @update $inc : campaignGivenAmount: size , callback


emailChecker: no need to keep all the list in redis

--- workers/social/lib/social/models/user/emailchecker.coffee
+++ workers/social/lib/social/models/user/emailchecker.coffee
@@ -768,10 +768,7 @@

   redisClient.smembers REDIS_KEY, (err, domains)->

-    if not err and domains.length is 0
-      redisClient.sadd REDIS_KEY, DOMAINS
-
-    DOMAINS = domains
+    DOMAINS.push domain for domain in domains when domain not in DOMAINS

 syncWithRedis()


Added 'fireonce' to legacy conversion for delay operators

--- src/configuration-generator.coffee
+++ src/configuration-generator.coffee
@@ -219,6 +219,8 @@
     if config.type == 'operation:throttle'
       config.repeat = config.interval
       delete config.interval
+    if config.type == 'operation:delay'
+      config.fireOnce = true

     return config


Luca.instanceCache method

Luca.instanceCache method

--- src/core/registry.coffee
+++ src/core/registry.coffee
@@ -102,7 +102,7 @@

 Luca.registry.findInstancesByClass = (componentClass)->
   Luca.registry.findInstancesByClassName( componentClass.displayName )
-
+
 Luca.registry.findInstancesByClassName = (className)->
   className = className.displayName if not _.isString( className )
   instances = Luca.registry.instances()
@@ -119,7 +119,7 @@
       className: className
       ctype: ctype

-Luca.cache = Luca.instanceCache = (cacheKey, object)->
+Luca.cache = Luca.cacheInstance = (cacheKey, object)->
   return unless cacheKey?
   return object if object.doNotCache is true


Add support for GNU Eqn extensions

Add support for GNU Eqn extensions

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -2222,6 +2222,32 @@
 				(above|back|bar|bold|ccol|col|cpile|define|delim|dot|dotdot|down|dyad|fat|font|from
 				|fwd|gfont|gsize|hat|italic|lcol|left|lineup|lpile|mark|matrix|ndefine|over|pile
 				|rcol|right|roman|rpile|size|sqrt|sub|sup|tdefine|tilde|to|under|up|vec)\\b"""
+		},{
+			# GNU Eqn: Keywords
+			name: "keyword.language.eqn.gnu.roff"
+			match: """(?x)\\b
+				(accent|big|chartype|smallover|type|vcenter|uaccent|split|nosplit
+				|opprime|special|sdefine|include|ifdef|undef|g[rb]font|space)\\b"""
+		},{
+
+			# GNU Eqn: Character names
+			name: "constant.language.greek-letter.eqn.gnu.roff"
+			match: """(?x)\\b
+				(Alpha|Beta|Chi|Delta|Epsilon|Eta|Gamma|Iota|Kappa|Lambda|Mu|Nu
+				|Omega|Omicron|Phi|Pi|Psi|Rho|Sigma|Tau|Theta|Upsilon|Xi|Zeta)\\b"""
+		},{
+
+			# GNU Eqn: Set MathML variables
+			name: "meta.set-variable.eqn.gnu.roff"
+			match: """(?x)\\b(set)[ \t]+
+				(accent_width|axis_height|baseline_sep|big_op_spacing[1-5]|body_depth|body_height|column_sep
+				|default_rule_thickness|delimiter_factor|delimiter_shortfall|denom[12]|draw_lines|fat_offset
+				|matrix_side_sep|medium_space|minimum_size|nroff|null_delimiter_space|num[12]|over_hang
+				|script_space|shift_down|su[bp]_drop|sub[12]|sup[1-3]|thick_space|thin_space|x_height)\\b"""
+			captures:
+				1: name: "storage.type.var.eqn.roff"
+				2: name: "variable.other.mathml.eqn.roff"
+
 		}, include: "#string"]


Allowing to constrain the selection to the given highlight interval.

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -372,21 +372,34 @@
                 .append("div")
                     .attr("class", "reload-arrow")

-
-
     ###
     ## Private API
     ###

     checkBrush: ->
-        if @selectionConstraint
-            [a, b] = @selectionConstraint
+        if @selectionConstraint or @highlightInterval?.constrain
+            [a, b] = @selectionConstraint if @selectionConstraint
+
+            if not @selectionConstraint
+                [a, b] = [@highlightInterval.start, @highlightInterval.end]
+
+            if @highlightInterval?.constrain
+                [a, b] = [
+                    if a > @highlightInterval.start then a else @highlightInterval.start,
+                    if b < @highlightInterval.end then b else @highlightInterval.end
+                ]
+
             [x, y] = @brush.extent()
+            [x, y] = [y, x] if x > y

             if x < a
                 x = a
+            if x > b
+                x = b
             if y > b
                 y = b
+            if y < a
+                y = a

             @brush.extent([x, y])

@@ -752,7 +765,7 @@

     setBinTooltipFormatter: (@binTooltipFormatter) ->

-    setHighlightInterval: (start, end, fillColor, strokeColor, outsideColor) ->
+    setHighlightInterval: (start, end, fillColor, strokeColor, outsideColor, constrain = false) ->
         if start and end
             @highlightInterval =
                 start: start
@@ -760,6 +773,10 @@
                 fillColor: fillColor
                 strokeColor: strokeColor
                 outsideColor: outsideColor
+                constrain: constrain
+
+            if constrain
+                @checkBrush()
         else
             @highlightInterval = null


remove firstLineMatch

remove firstLineMatch

--- grammars/tikz.cson
+++ grammars/tikz.cson
@@ -3,7 +3,7 @@
 filetypes: [
   'tikz'
 ]
-firstLineMatch: '\\\\begin\\{'
+# firstLineMatch: '\\\\begin\\{'
 limitLineLength: false

 patterns: [


Clean Atom package list

Clean Atom package list

--- atom/packages.cson
+++ atom/packages.cson
@@ -3,12 +3,9 @@
   "editorconfig"
   "file-icons"
   "highlight-selected"
-  "language-docker"
-  "language-typescript-grammars-only"
   "linter"
   "linter-coffeelint"
   "linter-csslint"
-  "linter-docker"
   "linter-flake8"
   "linter-htmlhint"
   "linter-js-yaml"
@@ -18,7 +15,6 @@
   "linter-php"
   "linter-scss-lint"
   "linter-shellcheck"
-  "linter-tslint"
   "linter-write-good"
   "linter-xmllint"
   "minimap"


Add snippet for ignoring entry point JS functions

With ESLint.

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -350,6 +350,10 @@
     'prefix': 'eslintdisablenextrule'
     'body': '/* eslint-disable-next-line $1 */$2'

+  'eslint no unused vars':
+    'prefix': 'eslintnounusedvars'
+    'body': '/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "${1:main}" }] */$2'
+
   # Mocha

   'it':


:fire: Remove test focus

:fire: Remove test focus

--- spec/color-project-spec.coffee
+++ spec/color-project-spec.coffee
@@ -669,7 +669,7 @@
               popup.ignoreRulesEditor.insertText(', +(')
               popup.ignoreRulesEditor.getBuffer().emitter.emit('did-stop-changing')

-            fit 'ignores the invalid rules', ->
+            it 'ignores the invalid rules', ->
               expect(popup.querySelectorAll('li.active').length).toEqual(6)

 ##    ########  ######## ########    ###    ##     ## ##       ########


Define search fields for projects

Define search fields for projects

--- app/assets/javascripts/controllers/projects/index.coffee
+++ app/assets/javascripts/controllers/projects/index.coffee
@@ -2,7 +2,16 @@
   baseRouteName: 'projects'

   search:
-    pg_search: null
+    query: null
+    between_created_at:
+      starts_at: null
+      ends_at: null
+    between_expires_at:
+      starts_at: null
+      ends_at: null
+    between_online_date:
+      starts_at: null
+      ends_at: null

 Dashboard.ProjectsSearchController = Dashboard.ProjectsTabController.extend Dashboard.SearchableController,
   baseRouteName: 'projects'


revert width change on TwoTitleBar

revert width change on TwoTitleBar

--- Framework/Views/Controls/TwoLineTitle_Framework_iOS.coffee
+++ Framework/Views/Controls/TwoLineTitle_Framework_iOS.coffee
@@ -36,7 +36,6 @@

     @view = Ti.UI.createView({
       top: 0
-      width: Ti.UI.SIZE
     })
     @view.add(@title)
     @view.add(@subtitle)


LEV-5 minSize adde

LEV-5 minSize adde

--- modules/levelcreator/app/assets/javascripts/level_creator/plugins/draw_art_cells.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugins/draw_art_cells.coffee
@@ -36,10 +36,12 @@
   constructor : () ->

-  execute : ({ input : { rgba, segments, relativeTime, dimensions, mission }, startShape, fillColor, strokeColor, hitMode, lineWidth, colorRandom, customTime, reverse, endPosition, size, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, mergeSegments}) ->
+  execute : ({ input : { rgba, segments, relativeTime, dimensions, mission }, minSize, startShape, fillColor, strokeColor, hitMode, lineWidth, colorRandom, customTime, reverse, endPosition, size, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, mergeSegments}) ->

     width = dimensions[0]
     height = dimensions[1]
+
+    minSize = 0 unless minSize?

     hitMode = false unless hitMode?
     lineWidth = 0 unless lineWidth?
@@ -65,8 +67,8 @@

     activeSegments = _.filter(segments, (segment) -> segment.display is true)

-    @setStartPaths(activeSegments, width, height, startShape)
-    @setEndPaths(activeSegments, width, height, endPosition, size, mergeSegments)
+    @setStartPaths(activeSegments, width, height, startShape, minSize)
+    @setEndPaths(activeSegments, width, height, endPosition, size, mergeSegments, minSize)

     endValues = [mission.start.id]
     for possibleEnd in mission.possibleEnds
@@ -147,7 +149,7 @@
     rgba

-  setStartPaths : (segments, width, height, startShape) ->
+  setStartPaths : (segments, width, height, startShape, minSize) ->

     values = _.pluck(segments, "id")

@@ -164,7 +166,7 @@
           width,
           height,
           segment.weightedCenter,
-          Math.sqrt(segment.size) / Math.PI
+          Math.max(Math.sqrt(segment.size) / Math.PI, minSize)
         )

     else
@@ -173,7 +175,7 @@
         segment.startPath = segment.path

-  setEndPaths : (segments, width, height, endPosition, size, mergeSegments) ->
+  setEndPaths : (segments, width, height, endPosition, size, mergeSegments, minSize) ->

     if mergeSegments
       values = _.pluck(segments, "value")
@@ -233,7 +235,7 @@
           width,
           height,
           segment.weightedCenter,
-          size || Math.sqrt(segment.size) / Math.PI
+          size || Math.max(Math.sqrt(segment.size) / Math.PI, minSize)
         )

@@ -244,7 +246,7 @@
     if size? and size > 0
       radius = size
     else
-      radius = Math.sqrt(segment.size) * 0.5
+      radius = Math.max(Math.sqrt(segment.size) / Math.PI, @MIN_CELL_SIZE)
     count = segment.path.length * 0.5

     mx = position.x


config: forced recipient is added into maindev

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -106,7 +106,7 @@

   customDomain        = { public: "#{scheme}://#{host}", public_: host, local: "http://#{local}", local_: "#{local}", host: "http://#{hostname}", port: 8090 }

-  email               = { host:     "#{customDomain.public_}"                     , defaultFromMail:    'hello@koding.com'                      , defaultFromName:    'Koding'                    , forcedRecipient: "#{process.env.USER}@koding.com"                       }
+  email               = { host:     "#{customDomain.public_}"                     , defaultFromMail:    'hello@koding.com'                      , defaultFromName:    'Koding'                    , forcedRecipientEmail: "#{process.env.USER}@koding.com", forcedRecipientUsername: "#{process.env.USER}"                      } #"#{process.env.USER}@koding.com"
   kontrol             = { url:      "#{customDomain.public}/kontrol/kite"         , port:               3000                                    , useTLS:             no                          , certFile:        ""                                   , keyFile:  ""                          , publicKeyFile: "./certs/test_kontrol_rsa_public.pem"    , privateKeyFile: "./certs/test_kontrol_rsa_private.pem"}
   broker              = { name:     "broker"                                      , serviceGenericName: "broker"                                , ip:                 ""                          , webProtocol:     "http:"                              , host:     "#{customDomain.public}"    , port:          8008                                     , certFile:       ""                                       , keyFile:         ""          , authExchange: "auth"                , authAllExchange: "authAll" , failoverUri: "#{customDomain.public}" }
   regions             = { kodingme: "#{configName}"                               , vagrant:            "vagrant"                               , sj:                 "sj"                        , aws:             "aws"                                , premium:  "vagrant"                 }
@@ -232,7 +232,7 @@
     webserver                      : {port          : 8080                , useCacheHeader: no                     , kitePort          : 8860}
     authWorker                     : {login         : "#{rabbitmq.login}" , queueName : socialQueueName+'auth'     , authExchange      : "auth"                                  , authAllExchange : "authAll"                                      , port  : 9530 }
     mq                             : mq
-    emailWorker                    : {cronInstant   : '*/10 * * * * *'    , cronDaily : '0 10 0 * * *'             , run               : no                                      , forcedRecipient: email.forcedRecipient                           , maxAge: 3      , port  : 9540 }
+    emailWorker                    : {cronInstant   : '*/10 * * * * *'    , cronDaily : '0 10 0 * * *'             , run               : no                                      , forcedRecipientEmail: email.forcedRecipientEmail         , forcedRecipientUsername: email.forcedRecipientUsername               , maxAge: 3      , port  : 9540 }
     elasticSearch                  : {host          : "#{boot2dockerbox}" , port      : 9200                       , enabled           : no                                      , queue           : "elasticSearchFeederQueue"}
     social                         : {port          : 3030                , login     : "#{rabbitmq.login}"        , queueName         : socialQueueName                         , kitePort        : 8760 }
     email                          : email


Add core.reopenProjectMenuCount translation

--- def/settings.coffee
+++ def/settings.coffee
@@ -170,6 +170,11 @@
         desc: "Le répertoire contenant vos projets. Les packages créés à l'aide de Package Generator seront enregistrés dans celui-ci par défaut."
       }
       {
+        id: 'core.reopenProjectMenuCount'
+        title: "Nombre de project récent"
+        desc: "Nombre de project récent à afficher dans le menu Réouvrir un project."
+      }
+      {
         id: 'core.telemetryConsent'
         title: "Envoyer des rapports à l'équipe d'Atom"
         desc: "Permets d'envoyer des rapports de statistiques et d'exceptions à l'équipe d'Atom afin d'aider à l'amélioration du produit."


headers values case-sensitiv

headers values case-sensitiv

--- src/hit-validator.coffee
+++ src/hit-validator.coffee
@@ -31,7 +31,7 @@
     transformedHeaders = {}

     for key, value of headers
-      transformedHeaders[key.toLowerCase()] = value.toLowerCase()
+      transformedHeaders[key.toLowerCase()] = value

     return transformedHeaders


Remove unused field

Remove unused field

--- SingularityUI/app/models/TaskHistory.coffee
+++ SingularityUI/app/models/TaskHistory.coffee
@@ -54,8 +54,6 @@

         taskHistory.isCleaning = _.last( taskHistory.taskUpdates ).taskState is 'TASK_CLEANING'

-        taskHistory.alerts = []
-
         taskHistory

     parseResources: (task) ->


Fix emitter in same server

Fix emitter in same server

--- server/stream/streamBroadcast.coffee
+++ server/stream/streamBroadcast.coffee
@@ -30,7 +30,7 @@
 			emitters[streamName] = stream.emit
 			stream.emit = (eventName, args...) ->
 				broadcast streamName, eventName, args
-				emitters[streamName].call {}, arguments
+				emitters[streamName].apply {}, arguments

 	Meteor.methods
 		stream: (streamName, eventName, args) ->


Added checkbox to filter by availability.

--- public/main.coffee
+++ public/main.coffee
@@ -1,6 +1,7 @@
 DEFAULT_SETTINGS =
   sort: "goodreads_rating"
   onlyfiction: "yes"
+  onlyavailable: "no"

 SORT_FIELDS = [
   ["goodreads_rating", "Goodreads Rating"]
@@ -30,7 +31,7 @@
   render: ->
     React.DOM.div className: "app",
       Settings(settings: @state.settings)
-      BookList(books: @state.books, sort: @state.settings.sort, onlyfiction: @state.settings.onlyfiction)
+      BookList(books: @state.books, sort: @state.settings.sort, onlyfiction: @state.settings.onlyfiction, onlyavailable: @state.settings.onlyavailable)

 BookList = React.createClass
   displayName: "BookList"
@@ -38,6 +39,7 @@
   render: ->
     books = @props.books
     books = (b for b in books when b.fiction) if @props.onlyfiction is "yes"
+    books = (b for b in books when b.available) if @props.onlyavailable is "yes"
     books = _.sortBy books, (b) =>
       if @props.sort.indexOf("rating") isnt -1
         -b[@props.sort]
@@ -45,13 +47,13 @@
         b[@props.sort]

     React.DOM.ul className: "book-list",
-      BookListItem(key: book.vpl_id, book: book, showFiction: @props.onlyfiction is "no") for id, book of books
+      BookListItem(key: book.vpl_id, book: book, showFiction: @props.onlyfiction is "no", showAvailability: @props.onlyavailable is "no") for id, book of books

 BookListItem = React.createClass
   displayName: "BookListItem"

   shouldComponentUpdate: (nextProps, nextState) ->
-    @props.book isnt nextProps.book or @props.showFiction isnt nextProps.showFiction
+    @props.book isnt nextProps.book or @props.showFiction isnt nextProps.showFiction or @props.showAvailability isnt nextProps.showAvailability

   render: ->
     { vpl_id, title, vpl_url, author, availability, available, availability_url, holds, vpl_rating, isbn, img, goodreads_id, goodreads_url, goodreads_rating, amazon_url, amazon_rating, goodreads_categories, description, fiction } = @props.book
@@ -69,9 +71,10 @@
           for c in goodreads_categories when !(c in ["to-read", "currently-reading", "fiction", "non-fiction", "nonfiction", "book-club", "favorites", "novels", "novel", "before-goodreads", "to-buy", "finished"])
             React.DOM.li key: c, c
       React.DOM.div className: "metadata",
-        React.DOM.div className: "availability",
-          React.DOM.span className: "yesno #{if available then 'yes' else 'no'}", "Available:"
-          React.DOM.span className: "holds", "(#{holds.replace(/Holds: (\d+)/, '$1 holds')})" if holds
+        if @props.showAvailability
+          React.DOM.div className: "availability",
+            React.DOM.span className: "yesno #{if available then 'yes' else 'no'}", "Available:"
+            React.DOM.span className: "holds", "(#{holds.replace(/Holds: (\d+)/, '$1 holds')})" if holds
         if @props.showFiction
           React.DOM.div className: "fiction yesno #{if fiction then 'yes' else 'no'}", "Fiction:"
         # React.DOM.div className: "vpl-rating",
@@ -96,6 +99,7 @@
     React.DOM.form className: "settings",
       Dropdown(key: "sort", id: "sort-setting", value: @props.settings.sort, label: "Sort by:", options: SORT_FIELDS)
       Checkbox(key: "onlyfiction", id: "only-fiction-setting", value: @props.settings.onlyfiction, label: "Fiction only?")
+      Checkbox(key: "onlyavailable", id: "only-available-setting", value: @props.settings.onlyavailable, label: "Available sets only?")

 Dropdown = React.createClass
   displayName: "Dropdown"


wrapped query into an object as it's a promise and it interfered with deferred.resolve


removed tinymce line

removed tinymce line

--- app/assets/javascripts/locomotive/views/content_entries/_form_view.js.coffee
+++ app/assets/javascripts/locomotive/views/content_entries/_form_view.js.coffee
@@ -84,7 +84,6 @@

       name = $(textarea).attr('name')
       CKEDITOR.replace(name)
-      # $(textarea).tinymce(settings)

   enable_select_fields: ->
     @_select_field_view = new Locomotive.Views.Shared.Fields.SelectView model: @content_type


refs #33 Allow to run multiple instances of CompetitiveList in parallel.

--- app/assets/javascripts/voluntary/dom_manipulation/competitive_list.js.coffee
+++ app/assets/javascripts/voluntary/dom_manipulation/competitive_list.js.coffee
@@ -13,15 +13,15 @@
     @competitiveListOptions = options
     @competitiveListOptions ||= {}

-    $(document.body).on "click", ".competitive_list_start_link", (event) =>
+    $(document.body).on "click", "#{@competitiveListOptions['id']} > .competitive_list_start_link", (event) =>
       event.preventDefault()
       @start()

-    $(document.body).on "click", ".cancel_tournament_button", (event) =>
+    $(document.body).on "click", "#{@competitiveListOptions['id']}_buttons > .cancel_tournament_button", (event) =>
       event.preventDefault()
       @cancelTournament()

-    $(document.body).on "click", ".select_winner_button", (event) =>
+    $(document.body).on "click", "#{@competitiveListOptions['id']}_buttons > .select_winner_button", (event) =>
       event.preventDefault()
       @appointWinnerOfMatchByInput()

@@ -195,7 +195,7 @@
     if @currentMatch == null
       modalTitle = 'No matches to rate left.'
       modalFooterHtml = """
-

+

   <button type="button" class="cancel_tournament_button" class="btn">Save match results and close window</button>

 """
@@ -233,7 +233,7 @@

 """
       modalFooterHtml = """
-

+

   <button type="button" class="cancel_tournament_button" class="btn">Save match results and close window</button>
   <button type="button" class="select_winner_button" class="btn btn-primary">Submit</button>


Added SMSG_INIT_WORLD_STATES opcode

Added SMSG_INIT_WORLD_STATES opcode

--- src/wrathnet/expansions/wotlk/enums/world-opcode.coffee
+++ src/wrathnet/expansions/wotlk/enums/world-opcode.coffee
@@ -58,6 +58,7 @@

   @SMSG_SPELL_NON_MELEE_DAMAGE_LOG    = 0x0250

+  @SMSG_INIT_WORLD_STATES             = 0x02C2
   @SMSG_UPDATE_WORLD_STATE            = 0x02C3

   @SMSG_WEATHER                       = 0x02F4


[Jax.Mesh.Icosahedron] Writing tests for vertices

--- spec/javascripts/jax/builtin/meshes/icosahedron_spec.js.coffee
+++ spec/javascripts/jax/builtin/meshes/icosahedron_spec.js.coffee
@@ -1,37 +1,46 @@

-getIcosahedronVerticesAsVectors = (icosahedron) ->
+###
+Costly, for testing

-  vertices = []
+@param  {Jax.Mesh}
+@return {Array} of 3D vectors
+###
+getMeshVerticesAsVectors = (mesh) ->
+  [vertices, vector] = [ [], [] ]
   i = 0
-  v = [];

-  for f in icosahedron.data.vertexWrapper.buffer
+  for coordinate in mesh.data.vertexWrapper.buffer # or mesh.data.vertexBuffer ?

-    v.push f
+    vector.push coordinate
     i++

     if i > 2
-      vertices.push( vec3.create v )
-      i = 0
-      v = []
+      vertices.push( vec3.create vector )
+      vector.length = i =0

   vertices

-unique = (objAry) -> # fixme
+###
+Returns an array containing the elements of iterable, without any doubles
+Todo: make results of the same type as iterable
+
+@param {Array|Object} iterable
+@return {Array}
+###
+unique = (iterable) ->
   results = []

-  valMatch = (seen, obj) ->
-    for other in seen
+  contains = (haystack, needle) ->
+    for straw in haystack
       match = true
-      for key, val of obj
-        match = false unless other[key] == val
+      for value, key in needle
+        match = false unless straw[key] == value
       return true if match
     false

-  objAry.forEach (item) ->
-    unless valMatch(results, item)
-      results.push(item)
+  for item in iterable
+    results.push(item) unless contains(results, item)

   results

@@ -62,9 +71,6 @@
       #icosa.rebuild()

-    # Is there a (high-level) way of getting the faces ?
-    # Is it the model's job ?
-
     it "should be 20", ->
       #fixme

@@ -77,17 +83,17 @@

   describe "its vertices", ->

-    vertices = null;
+    vertices = uniqueVertices = null;

     beforeEach ->
-      vertices = getIcosahedronVerticesAsVectors icosa
+      vertices = getMeshVerticesAsVectors icosa
+      uniqueVertices = unique vertices

-    it "should be 12", ->
-      console.log 'vertices', vertices
-      expect(vertices.length).toBe(60) # 20 faces, 3 vertices each
-      # test that the 60 vertices are actually 12
-#      uniqueVertices = unique vertices
-#      expect(uniqueVertices.length).toBe(12)
+    it "should be 60 overall (20 faces, 3 vertices each)", ->
+      expect(vertices.length).toBe(60)
+
+    it "should regroup in 12 unique vertices", ->
+      expect(uniqueVertices.length).toBe(12)

     it "should all have the size of the icosahedron as length", ->
       for v in vertices


Update Drag/drop example.

Update Drag/drop example.

--- examples/dragdrop.coffee
+++ examples/dragdrop.coffee
@@ -41,7 +41,7 @@

   # Events update the model, but should not update the DOM (render does that).
   events:
-    dropped: (event, drop) ->
-      @model.drops.push drop
+    drop: (d) ->
+      @model.drops.push d

   template: -> '

'


activity feed: skip non bucket entries when iterating

--- client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -174,6 +174,10 @@
   updateBuckets:(follower, followee)->
     for item in @itemsOrdered
       data = item.getData()
+
+      continue  unless data.group
+      continue  if typeof data.group is "string"
+
       if data.group[0].constructorName is followee.bongo_.constructorName
         if data.anchor.id is follower.id
           data.group.unshift {


addコマンドを追加

addコマンドを追加


Fixup port of InStateMenu#overlay's opacity.

--- src/in-state-menu.coffee
+++ src/in-state-menu.coffee
@@ -25,7 +25,7 @@
       @group = @add.group null, 'in-state-menu', yes

       @overlay = @add.graphics 0, 0, @group
-      @overlay.beginFill 0x000000, 0.2
+      @overlay.beginFill 0x000000, 0.8
       @overlay.drawRect 0, 0, @width, @height
       @overlay.endFill()


Don't add routes to transaction if null or undefined

--- src/middleware/messageStore.coffee
+++ src/middleware/messageStore.coffee
@@ -105,7 +105,8 @@
   # assign new transactions status to ctx object
   ctx.transactionStatus = status

-  update = { response: res, status: status, routes: ctx.routes }
+  update = { response: res, status: status }
+  update.routes = ctx.routes if ctx.routes?

   if ctx.mediatorResponse
     update.orchestrations = ctx.mediatorResponse.orchestrations if ctx.mediatorResponse.orchestrations


removed channel-id header

removed channel-id header

--- src/middleware/messageStore.coffee
+++ src/middleware/messageStore.coffee
@@ -65,7 +65,6 @@
     else
       ctx.transactionId = tx._id
       ctx.header['X-OpenHIM-TransactionID'] = tx._id.toString()
-      ctx.header['channel-id'] = tx.channelID
       return done null, tx

 exports.storeResponse = (ctx, done) ->