Translated 'menu_linux.cson' to Hebrew

Translated 'menu_linux.cson' to Hebrew

--- def/he/menu_linux.cson
+++ def/he/menu_linux.cson
@@ -1,305 +1,305 @@
 Menu:
   "&File":
-    value: "&File"
+    value: "&קובץ"
     submenu:
       "New &Window":
-        value: "New &Window"
+        value: "חלון &חדש"
       "&New File":
-        value: "&New File"
+        value: "קובץ &חדש"
       "&Open File…":
-        value: "&Open File…"
+        value: "&פתח קובץ"
       "Open Folder…":
-        value: "Open Folder…"
+        value: "פתח תיקייה"
       "Add Project Folder…":
-        value: "Add Project Folder…"
+        value: "הוסף תיקיית פרוייקט"
       "Reopen Project":
-        value: "Reopen Project"
+        value: "פתח פרוייקט מחדש"
         submenu:
           "Clear Project History":
-            value: "Clear Project History"
+            value: "נקה היסטוריית פרוייקט"
       "Reopen Last &Item":
-        value: "Reopen Last &Item"
+        value: "פתח מחדש &פריט אחרון"
       "&Save":
-        value: "&Save"
+        value: "&שמור"
       "Save &As…":
-        value: "Save &As…"
+        value: "שמור &בשם"
       "Save A&ll":
-        value: "Save A&ll"
+        value: "שמור &הכל"
       "&Close Tab":
-        value: "&Close Tab"
+        value: "&סגור לשונית"
       "Close &Pane":
-        value: "Close &Pane"
+        value: "סגור &חלונית"
       "Clos&e Window":
-        value: "Clos&e Window"
+        value: "סגור &חלון"
       "Quit":
-        value: "Quit"
+        value: "צא"
       "Close All Tabs":
-        value: "Close All Tabs"
+        value: "סגור את כל הכרטיסיות"
   "&Edit":
     value: "&Edit"
     submenu:
       "&Undo":
-        value: "&Undo"
+        value: "&בטל"
       "&Redo":
-        value: "&Redo"
+        value: "&בצע מחדש"
       "&Cut":
-        value: "&Cut"
+        value: "גז&ור"
       "C&opy":
-        value: "C&opy"
+        value: "&העתק"
       "Copy Pat&h":
-        value: "Copy Pat&h"
+        value: "העתק נ&תיב"
       "&Paste":
-        value: "&Paste"
+        value:"&הדבק"
       "Select &All":
-        value: "Select &All"
+        value: "בחר &הכל"
       "&Toggle Comments":
-        value: "&Toggle Comments"
+        value: "&הפעל/כבה תגובות"
       Lines:
-        value: "Lines"
+        value: "שורות"
         submenu:
           "&Indent":
-            value: "&Indent"
+            value: "&הזחה"
           "&Outdent":
-            value: "&Outdent"
+            value: "&הזחה החוצה"
           "&Auto Indent":
-            value: "&Auto Indent"
+            value: "&הזחה אוטומטית"
           "Move Line &Up":
-            value: "Move Line &Up"
+            value: "העבר שורה &מעלה"
           "Move Line &Down":
-            value: "Move Line &Down"
+            value: "העבר שורה &מטה"
           "Du&plicate Lines":
-            value: "Du&plicate Lines"
+            value: "&שכפל שורות"
           "D&elete Line":
-            value: "D&elete Line"
+            value: "&מחק שורה"
           "&Join Lines":
-            value: "&Join Lines"
+            value: "&חבר שורות יחד"
       Columns:
-        value: "Columns"
+        value: "עמודות"
         submenu:
           "Move Selection &Left":
-            value: "Move Selection &Left"
+            value: "הזז בחירה &שמאלה"
           "Move Selection &Right":
-            value: "Move Selection &Right"
+            value: "הזז בחירה &ימינה"
       Text:
-        value: "Text"
+        value: "טקסט"
         submenu:
           "&Upper Case":
-            value: "&Upper Case"
+            value: "&אותיות גדולות"
           "&Lower Case":
-            value: "&Lower Case"
+            value: "&אותיות קטנות"
           "Delete to End of &Word":
-            value: "Delete to End of &Word"
+            value: "מחק עד סוף &המילה"
           "Delete to Previous Word Boundary":
-            value: "Delete to Previous Word Boundary"
+            value: "מחק עד סוף המילה הקודמת"
           "Delete to Next Word Boundary":
-            value: "Delete to Next Word Boundary"
+            value: "מחק עד סוף המילה הבאה"
           "&Delete Line":
-            value: "&Delete Line"
+            value: "&מחק שורה"
           "&Transpose":
-            value: "&Transpose"
+            value: "&שנה"
       Folding:
-        value: "Folding"
+        value: "קיפול"
         submenu:
           "&Fold":
-            value: "&Fold"
+            value: "&קפל"
           "&Unfold":
-            value: "&Unfold"
+            value: "&בטל קיפול"
           "Unfold &All":
-            value: "Unfold &All"
+            value: "בטל קיפול &להכל"
           "Fol&d All":
-            value: "Fol&d All"
+            value: "קפל &הכל"
           "Fold Level 1":
-            value: "Fold Level 1"
+            value: "קיפול שלב 1"
           "Fold Level 2":
-            value: "Fold Level 2"
+            value: "קיפול שלב 2"
           "Fold Level 3":
-            value: "Fold Level 3"
+            value: "קיפול שלב 3"
           "Fold Level 4":
-            value: "Fold Level 4"
+            value: "קיפול שלב 4"
           "Fold Level 5":
-            value: "Fold Level 5"
+            value: "קיפול שלב 5"
           "Fold Level 6":
-            value: "Fold Level 6"
+            value: "קיפול שלב 6"
           "Fold Level 7":
-            value: "Fold Level 7"
+            value: "קיפול שלב 7"
           "Fold Level 8":
-            value: "Fold Level 8"
+            value: "קיפול שלב 8"
           "Fold Level 9":
-            value: "Fold Level 9"
+            value: "קיפול שלב 9"
       "&Preferences":
-        value: "&Preferences"
+        value: "&העדפות"
       "Config…":
-        value: "Config…"
+        value: "הגדר"
       "Init Script…":
-        value: "Init Script…"
+        value: "סקריפט התחלתי"
       "Keymap…":
-        value: "Keymap…"
+        value: "קובץ קיצורי המקלדת"
       "Snippets…":
-        value: "Snippets…"
+        value: "תגזירים"
       "Stylesheet…":
-        value: "Stylesheet…"
+        value: "גליון סגנונות"
       "Reflow Selection":
-        value: "Reflow Selection"
+        value: "בחר מחדש"
       Bookmark:
-        value: "Bookmark"
+        value: "סימניה"
         submenu:
           "View All":
-            value: "View All"
+            value: "הצג הכל"
           "Toggle Bookmark":
-            value: "Toggle Bookmark"
+            value: "הפעל/כבה סימניה"
           "Jump to Next Bookmark":
-            value: "Jump to Next Bookmark"
+            value: "קפוץ לסימניה הבאה"
           "Jump to Previous Bookmark":
-            value: "Jump to Previous Bookmark"
+            value: "קפוץ לסימניה הקודמת"
       "Select Encoding":
-        value: "Select Encoding"
+        value: "בחר קידוד"
       "Go to Line":
-        value: "Go to Line"
+        value: "לך לשורה"
       "Select Grammar":
-        value: "Select Grammar"
+        value: "בחר דקדוק"
   "&View":
-    value: "&View"
+    value: "&מראה"
     submenu:
       "Toggle &Full Screen":
-        value: "Toggle &Full Screen"
+        value: "הפעל/כבה &מסך מלא"
       "Toggle Menu Bar":
-        value: "Toggle Menu Bar"
+        value: "הפעל/כבה שורת תפריט"
       Panes:
-        value: "Panes"
+        value: "חלוניות"
         submenu:
           "Split Up":
-            value: "Split Up"
+            value: "פצל מעלה"
           "Split Down":
-            value: "Split Down"
+            value: "פצל מטה"
           "Split Left":
-            value: "Split Left"
+            value: "פצל שמאלה"
           "Split Right":
-            value: "Split Right"
+            value: "פצל ימינה"
           "Focus Next Pane":
-            value: "Focus Next Pane"
+            value: "התפקס על החלונית הבאה"
           "Focus Previous Pane":
-            value: "Focus Previous Pane"
+            value: "התפקס על החלונית הקודמת"
           "Focus Pane Above":
-            value: "Focus Pane Above"
+            value: "התפקס על החלונית מלמעלה"
           "Focus Pane Below":
-            value: "Focus Pane Below"
+            value: "התפקס על החלונית מלמטה"
           "Focus Pane On Left":
-            value: "Focus Pane On Left"
+            value: "התפקס על החלונית משמאל"
           "Focus Pane On Right":
-            value: "Focus Pane On Right"
+            value: "התפקס על החלונית מימין"
           "Close Pane":
-            value: "Close Pane"
+            value: "סגור חלונית"
       Developer:
-        value: "Developer"
+        value: "מפתח"
         submenu:
           "Open In &Dev Mode…":
-            value: "Open In &Dev Mode…"
+            value: "פתח במצב &פיתוח"
           "&Reload Window":
-            value: "&Reload Window"
+            value: "&טען חלון מחדש"
           "Run Package &Specs":
-            value: "Run Package &Specs"
+            value: "הרץ &מפרט חבילות"
           "Run &Benchmarks":
-            value: "Run &Benchmarks"
+            value: "הרץ &בדיקות"
           "Toggle Developer &Tools":
-            value: "Toggle Developer &Tools"
+            value: "הפעל/כבה &אפשרויות פיתוח"
       "&Increase Font Size":
-        value: "&Increase Font Size"
+        value: "&הגדל גודל גופן"
       "&Decrease Font Size":
-        value: "&Decrease Font Size"
+        value: "&הקטן גודל גופן"
       "Re&set Font Size":
-        value: "Re&set Font Size"
+        value: "&אפס את גודל הגופן"
       "Toggle Soft &Wrap":
-        value: "Toggle Soft &Wrap"
+        value: "הפעל/כבה גלישת טקסט רכה"
       "Toggle Command Palette":
-        value: "Toggle Command Palette"
+        value: "הפעל/כבה לוח בקרה"
       "Toggle Tree View":
-        value: "Toggle Tree View"
+        value: "הפעל/כבה תצוגת עץ"
   "&Selection":
     value: "&Selection"
     submenu:
       "Add Selection &Above":
-        value: "Add Selection &Above"
+        value: "הוסף סימון &מלמעלה"
       "Add Selection &Below":
-        value: "Add Selection &Below"
+        value: "הוסף סימון &מלמטה"
       "S&plit into Lines":
-        value: "S&plit into Lines"
+        value: "&פצל סימון לשורות"
       "Single Selection":
-        value: "Single Selection"
+        value: "בחירה יחידה"
       "Select to &Top":
-        value: "Select to &Top"
+        value: "בחר עד החלק &העליון"
       "Select to Botto&m":
-        value: "Select to Botto&m"
+        value: "בחר עד החלק &התחתון"
       "Select &Line":
-        value: "Select &Line"
+        value: "בחר &שורה"
       "Select &Word":
-        value: "Select &Word"
+        value: "בחר &מילה"
       "Select to Beginning of W&ord":
-        value: "Select to Beginning of W&ord"
+        value: "בחר עד תחילת &המילה"
       "Select to Beginning of L&ine":
-        value: "Select to Beginning of L&ine"
+        value: "בחר עד תחילת &השורה"
       "Select to First &Character of Line":
-        value: "Select to First &Character of Line"
+        value: "בחר עד &התו הראשון בשורה"
       "Select to End of Wor&d":
-        value: "Select to End of Wor&d"
+        value: "בחר עד סוף &המילה"
       "Select to End of Lin&e":
-        value: "Select to End of Lin&e"
+        value: "בחר עד סוף &השורה"
       "Select Inside Brackets":
-        value: "Select Inside Brackets"
+        value: "בחר בתוך סוגריים"
   "F&ind":
-    value: "F&ind"
+    value: "מ&צא"
     submenu:
       "Find in Buffer":
-        value: "Find in Buffer"
+        value: "מצא במאגר"
       "Replace in Buffer":
-        value: "Replace in Buffer"
+        value: "החלף במאגר"
       "Select Next":
-        value: "Select Next"
+        value: "בחר הבא"
       "Select All":
-        value: "Select All"
+        value: "בחר הכל"
       "Toggle Find in Buffer":
-        value: "Toggle Find in Buffer"
+        value: "הפעל/כבה מציאה במאגר"
       "Find in Project":
-        value: "Find in Project"
+        value: "מצא בפרוייקט"
       "Toggle Find in Project":
-        value: "Toggle Find in Project"
+        value: "הפעל/כבה מציאה בפרוייקט"
       "Find All":
-        value: "Find All"
+        value: "מצא הכל"
       "Find Next":
-        value: "Find Next"
+        value: "מצא הבא"
       "Find Previous":
-        value: "Find Previous"
+        value: "מצא קודם"
       "Replace Next":
-        value: "Replace Next"
+        value: "החלף הבא"
       "Replace All":
-        value: "Replace All"
+        value: "החלף הכל"
       "Clear History":
-        value: "Clear History"
+        value: "נקה היסטוריה"
       "Find Buffer":
-        value: "Find Buffer"
+        value: "מצא מאגר"
       "Find File":
-        value: "Find File"
+        value: "מצא קובץ"
       "Find Modified File":
-        value: "Find Modified File"
+        value: "מצא קובץ ששונה"
   "&Packages":
-    value: "&Packages"
+    value: "&חבילות"
   "&Help":
-    value: "&Help"
+    value: "&עזרה"
     submenu:
       "View &Terms of Use":
-        value: "View &Terms of Use"
+        value: "הצג &תנאי שימוש"
       "View &License":
-        value: "View &License"
+        value: "הצג &רישיון"
       "&Documentation":
-        value: "&Documentation"
+        value: "&תיעוד"
       "Frequently Asked Questions":
-        value: "Frequently Asked Questions"
+        value: "שאלות נפוצות"
       "Community Discussions":
-        value: "Community Discussions"
+        value: "דיונים קהילתיים"
       "Report Issue":
-        value: "Report Issue"
+        value: "דווח על בעיה"
       "Search Issues":
-        value: "Search Issues"
+        value: "חפש בעיות"
       "About Atom":
-        value: "About Atom"
+        value: "אודות אטום"
       "Welcome Guide":
-        value: "Welcome Guide"
+        value: "מדריך למשתמש מתחיל"


簡短計算過程

簡短計算過程

坐公車想到的

--- javascript/bus.coffee
+++ javascript/bus.coffee
@@ -61,28 +61,16 @@
   now_date = new Date()
   now_day = now_date.getDay()
   now_minutes = now_date.getHours()*60+now_date.getMinutes()
-  if now_day is 3
-    for campus, table of time_table["wednesday"]
-      for info, minutes of table
-        if now_minutes < minutes
-          in_hours = (minutes - now_minutes) // 60
-          in_minutes = (minutes - now_minutes) % 60
-          message = switch
-            when info is "NA" then "本部末班車已過"
-            when in_hours is 0 then "#{info}（剩下 #{in_minutes} 分鐘）"
-            else "#{info}（剩下 #{in_hours} 小時 #{in_minutes} 分鐘）"
-          document.getElementById(campus).innerHTML = message
-          break
-  else
-    for campus, table of time_table["everyday"]
-      for info, minutes of table
-        if now_minutes < minutes
-          in_hours = (minutes - now_minutes) // 60
-          in_minutes = (minutes - now_minutes) % 60
-          message = switch
-            when info is "NA" then "本部末班車已過"
-            when in_hours is 0 then "（剩下 #{in_minutes} 分鐘）"
-            else "（剩下 #{in_hours} 小時 #{in_minutes} 分鐘）"
-          document.getElementById(campus).innerHTML = message
-          break
+  today_table = if now_day is 3 then time_table["wednesday"] else time_table["everyday"]
+  for campus, table of today_table
+    for info, minutes of table
+      if now_minutes < minutes
+        in_hours = (minutes - now_minutes) // 60
+        in_minutes = (minutes - now_minutes) % 60
+        message = switch
+          when info is "NA" then "本部末班車已過"
+          when in_hours is 0 then "#{info}（剩下 #{in_minutes} 分鐘）"
+          else "#{info}（剩下 #{in_hours} 小時 #{in_minutes} 分鐘）"
+        document.getElementById(campus).innerHTML = message
+        break
 setInterval(update_reminder(), 1000*60*10);


Initial unscoped support

Initial unscoped support

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -19,6 +19,7 @@

     options.buffered ?= false
     options.control ?= false
+    options.unscoped ?= false
     options.triggering ?= true

     if not process and options and options.process
@@ -118,7 +119,7 @@

   prepareBufferForIP: (ip) ->
     if @isAddressable()
-      if ip.scope?
+      if ip.scope? and not @options.unscoped
         @scopedBuffer[ip.scope] = [] unless ip.scope of @scopedBuffer
         @scopedBuffer[ip.scope][ip.index] = [] unless ip.index of @scopedBuffer[ip.scope]
         return @scopedBuffer[ip.scope][ip.index]
@@ -127,7 +128,7 @@
         return @iipBuffer[ip.index]
       @indexedBuffer[ip.index] = [] unless ip.index of @indexedBuffer
       return @indexedBuffer[ip.index]
-    if ip.scope?
+    if ip.scope? and not @options.unscoped
       @scopedBuffer[ip.scope] = [] unless ip.scope of @scopedBuffer
       return @scopedBuffer[ip.scope]
     if ip.initial
@@ -155,7 +156,7 @@

   getBuffer: (scope, idx, initial = false) ->
     if @isAddressable()
-      if scope?
+      if scope? and not @options.unscoped
         return undefined unless scope of @scopedBuffer
         return undefined unless idx of @scopedBuffer[scope]
         return @scopedBuffer[scope][idx]
@@ -164,7 +165,7 @@
         return @iipBuffer[idx]
       return undefined unless idx of @indexedBuffer
       return @indexedBuffer[idx]
-    if scope?
+    if scope? and not @options.unscoped
       return undefined unless scope of @scopedBuffer
       return @scopedBuffer[scope]
     if initial


Dismiss the build-invite prompt when we've sent one.

This doesn't persist across refreshes (yet) -- it's just on the frontend.

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -40,6 +40,7 @@
     rest_commits_visible: false
     node: []
     feature_flags: {}
+    invite_sent: false

   clean: () =>
     # pusher fills the console with errors if you unsubscribe
@@ -364,8 +365,9 @@
         @current_container(@containers()[0])

     @invites = @komp =>
-      if not @previous_successful_build() and @outcome() == "success" and VM.project()
-        new CI.inner.BuildInvite VM.project().users()
+      if not @invite_sent() and not @previous_successful_build() and @outcome() == "success" and VM.project()
+        new CI.inner.BuildInvite VM.project().users(), () =>
+          @invite_sent(true)

   feature_enabled: (feature_name) =>
     @feature_flags()[feature_name]
@@ -765,9 +767,8 @@
     for user in @users
       @inviting[user.login] false

-  constructor: (users, json={}) ->
+  constructor: (@users, @sent, json={}) ->
     super json
-    @users = users
     @inviting = {}
     # Prepopulate the list of team members to invite with the ones
     # whose email addresses we already know.
@@ -778,4 +779,5 @@
     to_invite = (user for user in @users when @inviting[user.login]() and user.email())
     if to_invite.length > 0
       VM.project().invite_team_members to_invite
-
+    @sent()
+


fix redirect and change copy to encourage click on the link

--- js/outer/error.coffee
+++ js/outer/error.coffee
@@ -15,7 +15,7 @@

   message: =>
     messages =
-      401: "You must log in to view this page"
+      401: "Login here log in to view this page"
       404: "We're sorry, but that page doesn't exist"
       500: "We're sorry, but something broke"
     messages[@status] or "Something completely unexpected happened"


Fix console.log calls

Fix console.log calls

--- main.coffee
+++ main.coffee
@@ -329,11 +329,13 @@
                                 callback null, records
                             .catch (err) ->
                                 if err instanceof Mockaroo.errors.InvalidApiKeyError
-                                    console.log 'invalid api key'
+                                    err = 'invalid api key'
                                 else if err instanceof Mockaroo.errors.UsageLimitExceededError
-                                    console.log 'usage limit exceeded'
+                                    err = 'usage limit exceeded'
                                 else
-                                    console.log 'unknow error', error
+                                    err = error
+
+                                @log err.red
                                 callback err
                     catch e
                         err = "[ERROR] Cannot initialize Mockaroo client -- #{e}".red


Unified 404 handling. Why not?

Unified 404 handling.
Why not?

--- main.coffee
+++ main.coffee
@@ -175,27 +175,23 @@

 app.get '/profile/id/:id/', (request, response) ->
 	id = parseInt request.param('id'), 10
-	utils.game.getUserCharacters mysqlConnection, id, (error, chars) ->
-		if error?
-			throw error
-		if chars is null
-			response.send 404
-			return
-		options = request.uonline.basicOpts
-		options.instance = 'profile'
-		options.profileIsMine = (options.loggedIn is true) and (id == options.userid)
-		for i of chars
-			options[i] = chars[i]
-		options.nickname = options.user # кастыль #273
-		response.render 'profile', options
+	chars = utils.game.getUserCharacters.sync null, mysqlConnection, id
+	if chars is null
+		throw new Error '404'
+	options = request.uonline.basicOpts
+	options.instance = 'profile'
+	options.profileIsMine = (options.loggedIn is true) and (id == options.userid)
+	for i of chars
+		options[i] = chars[i]
+	options.nickname = options.user # кастыль #273
+	response.render 'profile', options

 app.get '/profile/user/:nickname/', (request, response) ->
 	nickname = request.param('nickname')
 	chars = utils.game.getUserCharacters.sync null, mysqlConnection, nickname
 	if chars is null
-		response.send 404
-		return
+		throw new Error '404'
 	options = request.uonline.basicOpts
 	options.instance = 'profile'
 	options.profileIsMine = (options.loggedIn is true) and (chars.id == options.userid)
@@ -286,13 +282,16 @@

 # 404 handling
 app.get '*', (request, response) ->
-	quickRenderError request, response, 404
+	throw new Error '404'

 # Exception handling
 app.use (error, request, response, next) ->
-	console.error error.stack
-	quickRenderError request, response, 500
+	if error.message is '404'
+		quickRenderError request, response, 404
+	else
+		console.error error.stack
+		quickRenderError request, response, 500

 # main


Add comment explaining NDC validation

Add comment explaining NDC validation

--- src/script/Phone.coffee
+++ src/script/Phone.coffee
@@ -27,6 +27,7 @@
 				[foundNDC, ndcRegex] = @testNDC(nationalDestinationCode, countryObj, nationalNumber)
 				break if foundNDC is true

+		# We are not taking NDC validation into account if the country doesn't have NDC options.
 		if not foundNDC and countryObj.nationalDestinationCode.length > 0 then return null

 		withoutNDC = nationalNumber.replace(ndcRegex, "")


Teams: Search: Fetch and merge user emails of accounts from JUser.

--- client/admin/lib/views/members/teammemberscommonview.coffee
+++ client/admin/lib/views/members/teammemberscommonview.coffee
@@ -7,6 +7,7 @@
 KDCustomHTMLView     = kd.CustomHTMLView
 KDListViewController = kd.ListViewController
 KDHitEnterInputView  = kd.HitEnterInputView
+remote               = require('app/remote').getInstance()

 module.exports = class TeamMembersCommonView extends KDView
@@ -186,15 +187,33 @@
     @searchClear.show()

     kd.singletons.search.searchAccounts query, options
-      .then (accounts) =>
-        @resetListItems no  if @page is 0
-        @listMembers accounts
-        @isFetching = no
+      .then (accounts) => @handleSearchResult accounts
       .catch (err) =>
         @page = 0
         @handleError err

+  handleSearchResult: (accounts) ->
+
+    usernames = []
+
+    for account in accounts
+      usernames.push account.profile.nickname
+
+    # Send a request to back-end for user emails.
+    remote.api.JAccount.fetchEmailsByUsername usernames, (err, emails) =>
+
+      @resetListItems no  if err
+
+      for account in accounts
+        { profile }   = account
+        profile.email = emails[profile.nickname]
+
+      @resetListItems no  if @page is 0
+      @listMembers accounts
+      @isFetching = no
+
+
   resetListItems: (showLoader = yes) ->

     @listController.removeAllItems()


Only call ::checkForVisibilityChange on editor attachment, not ::pollDOM

I’m pretty sure ::pollDOM is subsumed by ::checkForVisibilityChange
because there’s no reason the editor would be considered visible prior
to being attached.

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -143,10 +143,6 @@
     return unless onDom
     return if @attached
     @attached = true
-    @component.pollDOM()
-
-    # Ensure that editor measurements like lineHeight are taken here, even if pollDOM() was a no-op
-    # because an update was requested.
     @component.checkForVisibilityChange()

     @focus() if @focusOnAttach


Finalised introduction admin modal.

Finalised introduction admin modal.

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -316,45 +316,8 @@
                     cssClass  : 'type-explain'
                     itemClass : KDView
                     partial   : 'This will show a timer.'
-
-
           "Introduction":
-            buttons           :
-              "Go"  :
-                title         : "Go"
-                style         : "modal-clean-gray"
-                loader        :
-                  color       : "#444444"
-                  diameter    : 12
-                callback      : (event)=>
-
-              "Cancel":
-                title         : "Cancel"
-                style         : "modal-clean-gray"
-                loader        :
-                  color       : "#444444"
-                  diameter    : 12
-                callback      : (event)=>
-
-            # fields            :
-            #   Title           :
-            #     label         :s "Message Title"
-            #     type          : "text"
-            #     placeholder   : "Shutdown in"
-            #     tooltip       :
-            #       title       : 'When using type "Restart", end title with "in",'+\
-            #       ' since there will be a timer following the title.'
-            #       placement   : 'right'
-            #       direction   : 'center'
-            #       offset      :
-            #         top       : 2
-            #         left      : 0
-            #   Description     :
-            #     label         : "Message Details"
-            #     type          : "text"
-            #     placeholder   : "We are upgrading the platform. Please save your work."
-
-
+            fields            : {}

     super options, data

@@ -365,6 +328,7 @@

     @initInviteTab()
     @initMigrateTab()
+    @initIntroductionTab()
     @createUserAutoComplete()

   createUserAutoComplete:->
@@ -428,3 +392,330 @@
     fields.Invites.show()
     inputs.Invites.setPlaceHolder 'Loading...'
     buttons.Update.show()
+
+  initIntroductionTab: ->
+    @modalTabs.forms["Introduction"].addSubView new IntroductionAdmin()
+
+
+class IntroductionAdmin extends JView
+
+  constructor: (options = {}, data) ->
+
+    super options, data
+
+    @buttonsContainer = new KDView
+      cssClass : "introduction-admin-buttons"
+
+    @buttonsContainer.addSubView @addButton = new KDButtonView
+      cssClass : "editor-button"
+      title    : "Add New"
+      callback : => @showForm()
+
+    @container = new KDView
+      cssClass : "introduction-admin-content"
+
+    @container.addSubView @loader = new KDLoaderView
+      size     :
+        width  : 36
+
+    @container.addSubView @notFoundText = new KDView
+      cssClass : "introduction-not-found"
+      partial  : "There is no introduction yet."
+    @notFoundText.hide()
+
+    @container.addSubView @introListContainer = new KDView
+
+    @fetchData()
+
+    @on "IntroductionItemDeleted", (snippet) =>
+      @snippets.splice @snippets.indexOf(snippet), 1
+      @notFoundText.show() if @snippets.length is 0
+
+  fetchData: ->
+    KD.remote.api.JIntroSnippet.fetchAll (err, snippets) =>
+      @loader.hide()
+      @snippets = snippets
+      return @notFoundText.show() if snippets.length is 0
+
+      for snippet in snippets
+        @introListContainer.addSubView new IntroductionItem
+          delegate: @
+        ,snippet
+
+  showForm: (type = "Group", data = @getData(), actionType = "Insert") ->
+    @buttonsContainer.hide()
+    @container.hide()
+    @addSubView @form = new IntroductionAdminForm {
+      type
+      actionType
+      delegate: @
+    }
+    , data
+
+  viewAppended: ->
+    super
+    @loader.show()
+
+  pistachio: ->
+    """
+      {{> @buttonsContainer}}
+      {{> @container}}
+    """
+
+class IntroductionItem extends JView
+
+  constructor: (options = {}, data) ->
+
+    options.cssClass = "admin-introduction-item"
+
+    super options, data
+
+    @createElements()
+
+  createElements: ->
+    data = @getData()
+
+    @title = new KDView
+      cssClass : "cell name"
+      partial  : data.title
+      click    : => @setupChilds()
+
+    @addLink = new KDCustomHTMLView
+      tagName  : "span"
+      partial  : "Add Into"
+      click    : => @add()
+
+    @updateLink = new KDCustomHTMLView
+      tagName  : "span"
+      partial  : "Update"
+      click    : => @update()
+
+    @deleteLink = new KDCustomHTMLView
+      tagName  : "span"
+      partial  : "Delete"
+      click    : => @remove()
+
+  add: ->
+    @getDelegate().showForm "Item", @getData()
+
+  update: ->
+    @getDelegate().showForm "Group", @getData(), "Update"
+
+  remove: ->
+    @getData().delete => @destroy()
+    @getDelegate().emit "IntroductionItemDeleted", @getData()
+
+  setupChilds: ->
+    if @childContainer
+      if @isChildContainerVisible
+        @childContainer.hide()
+        return @isChildContainerVisible = no
+      else
+        @childContainer.show()
+        return @isChildContainerVisible = yes
+    else
+      return if @ instanceof IntroductionChildItem
+      @addSubView @childContainer = new KDView
+      @isChildContainerVisible = yes
+
+      for snippet in @getData().snippets
+        @childContainer.addSubView new IntroductionChildItem delegate: @, snippet
+
+  pistachio: ->
+    data       = @getData()
+    hasOverlay = if data.overlay is yes then "yes" else "no"
+    """
+      {{> @title}}
+
#{data.expiryDate}

+
#{data.snippets.length}

+
#{hasOverlay}

+

+        {{> @addLink}}{{> @updateLink}}{{> @deleteLink}}
+

+    """
+
+class IntroductionChildItem extends IntroductionItem
+
+  constructor: (options = {}, data) ->
+
+    options.cssClass = "admin-introduction-child-item"
+
+    super options, data
+
+    @title = new KDView
+      cssClass : "cell name"
+      partial  : data.introTitle
+      click    : => @setupChilds()
+
+  remove: ->
+    @getDelegate().getData().deleteChild @getData().introId, =>
+      @destroy()
+
+  update: ->
+    introductionItem      = @getDelegate()
+    introductionAdmin     = introductionItem.getDelegate()
+    data                  = @getData()
+    data.introductionItem = introductionItem
+    introductionAdmin.showForm "Item", data, "Update"
+
+  pistachio: ->
+    data = @getData()
+    """
+      {{> @title}}
+
#{data.introId}

+

+        {{> @updateLink}}{{> @deleteLink}}
+

+    """
+
+class IntroductionAdminForm extends KDFormViewWithFields
+
+  constructor: (options = {}, data = {}) ->
+
+    @parentData = data
+
+    groupFields =
+      Title           :
+        label         : "Title"
+        type          : "text"
+        placeholder   : "Title of the introduction"
+        defaultValue  : data.title
+      ExpiryDate      :
+        label         : "Expiry Date"
+        type          : "text"
+        placeholder   : "Best before"
+        defaultValue  : data.expiryDate
+      Overlay         :
+        label         : "Overlay"
+        type          : "select"
+        defaultValue  : data.overlay
+        selectOptions : [
+          { title     : "Yes", value : "yes" }
+          { title     : "No",  value : "no"  }
+        ]
+      Visibility       :
+        label         : "Visibility"
+        type          : "select"
+        defaultValue  : data.visibility
+        selectOptions : [
+          { title     : "Show all together", value : "allTogether" }
+          { title     : "Show step by step", value : "stepByStep"  }
+        ]
+
+    itemFields =
+      IntroTitle      :
+        label         : "Intro Title"
+        type          : "text"
+        defaultValue  : data.introTitle
+      IntroId         :
+        label         : "Intro Id"
+        type          : "text"
+        placeholder   : "Parent view's intro id"
+        defaultValue  : data.introId
+      Snippet         :
+        label         : "Intro Snippet"
+        type          : "textarea"
+        defaultValue  : Encoder.htmlDecode data.snippet
+
+    options.fields  = itemFields
+    if options.type is "Group"
+      if options.actionType is "Insert"
+        groupFields[key] = field for key, field of itemFields
+      options.fields = groupFields
+
+    options.buttons =
+      "Save"          :
+        title         : "Save"
+        style         : "modal-clean-gray"
+        loader        :
+          color       : "#444444"
+          diameter    : 12
+        callback      : (event) => @save()
+      "Cancel"        :
+        title         : "Cancel"
+        style         : "modal-clean-gray"
+        loader        :
+          color       : "#444444"
+          diameter    : 12
+        callback      : (event) =>
+          @destroy()
+          @getDelegate().container.show()
+          @getDelegate().buttonsContainer.show()
+
+    super options, data
+
+  save: ->
+    {actionType} = @getOptions()
+
+    if @getOptions().type is "Group"
+      return @insertParent() if actionType is "Insert"
+      @updateParent()
+    else
+      return @insertChild() if actionType is "Insert"
+      @updateChild()
+
+  insertParent: ->
+    KD.remote.api.JIntroSnippet.create @createPostData(), =>
+      @buttons["Save"].hideLoader()
+      log "new created"
+
+  insertChild: ->
+    @parentData.addChild @createPostData(), =>
+      @buttons["Save"].hideLoader()
+      log "child created"
+
+  updateParent: ->
+    @parentData.update @createPostData(), =>
+      log "parent updated"
+
+  updateChild: ->
+    data = @createPostData()
+    data.oldIntroId = @parentData.introId
+    @parentData.introductionItem.getData().updateChild data, =>
+      log "child updated"
+
+    # {
+    #   introTitle : postData.IntroTitle
+    #   introId    : postData.IntroId
+    #   snippet    : postData.Snippet
+    #   oldIntroId : @getData().IntroId
+    # }
+    # @getData().snippet.updateChild @createPostData(), =>
+      # introId    = "EDITED"
+      # snippet    = "alert"
+      # introTitle = "TITLE"
+      # @getDelegate().getData().updateChild {
+      #   oldIntroId : @getData().introId
+      #   introId
+      #   introTitle
+      #   snippet
+      # }, => @setData {
+      #     introTitle
+      #     introId
+      #     snippet
+      #   }
+
+  createPostData: ->
+    {inputs}     = @
+    options      = @getOptions()
+    {type}       = options
+    {actionType} = options
+    itemData     = {}
+
+    if type is "Item" or (type is "Group" and actionType is "Insert")
+      itemData =
+        introId    : inputs.IntroId.getValue()
+        introTitle : inputs.IntroTitle.getValue()
+        snippet    : inputs.Snippet.getValue()
+
+    if type is "Group"
+      data = {
+        title      : inputs.Title.getValue()
+        expiryDate : inputs.ExpiryDate.getValue()
+        visibility : inputs.Visibility.getValue()
+        overlay    : inputs.Overlay.getValue()
+      }
+      data.snippets = [ itemData ] if actionType is "Insert"
+      return data
+
+    return itemData


:art: cleanup further

:art: cleanup further

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -178,7 +178,6 @@
   '7': 'vim-mode:set-count'
   '8': 'vim-mode:set-count'
   '9': 'vim-mode:set-count'
-  # '"': 'vim-mode:set-count'

 # normal
 # -------------------------
@@ -206,7 +205,8 @@

 # operator-pending, visual
 # -------------------------
-'atom-text-editor.vim-mode.operator-pending-mode, atom-text-editor.vim-mode.visual-mode':
+'atom-text-editor.vim-mode.operator-pending-mode,
+ atom-text-editor.vim-mode.visual-mode':
   'x': 'vim-mode:reset-normal-mode'
   '~': 'vim-mode:toggle-case'
   'U': 'vim-mode:upper-case'


Minor Platforms spec descriptions cleanup.

--- tests/platforms.coffee
+++ tests/platforms.coffee
@@ -29,7 +29,7 @@
       it 'should have empty ledges array', ->
         expect(platforms.ledges).toEqual []

-    describe '_createTileGeneratorState', ->
+    describe '#_createTileGeneratorState', ->
       state = null

       beforeEach -> state = platforms._createTileGeneratorState()
@@ -45,7 +45,7 @@
       it 'returns expected base number of ledges', ->
         expect(state.numLedgeRows).toBe 23

-    describe '_addLedgeDifficulty', ->
+    describe '#_addLedgeDifficulty', ->
       ledge = null
       vars = { numLedgeRows: 23 }

@@ -82,7 +82,7 @@

         expect(ledge.start).toBe 7

-    describe '_addRow', ->
+    describe '#_addRow', ->
       vars =
         iColStart: 0
         iColEnd: 3


Make keybinding more specific to match current style

Add .editor when checking if command mode has been activated

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -2,7 +2,7 @@
   'escape': 'vim-mode:activate-command-mode'
   'ctrl-[': 'vim-mode:activate-command-mode'

-'.platform-darwin .vim-mode:not(.command-mode)':
+'.platform-darwin .editor.vim-mode:not(.command-mode)':
   'ctrl-c': 'vim-mode:activate-command-mode'

 '.editor.vim-mode:not(.insert-mode)':


Default options.

Default options.

--- src/view.coffee
+++ src/view.coffee
@@ -17,7 +17,7 @@
     are passed in. Does the normal backbone constructor and then does the
     first state change.
     ###
-    constructor: (options) ->
+    constructor: (options={}) ->
         @children = []

         if options.className then @className = options.className


Extract Default Modules

Extract Default Modules

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -156,7 +156,7 @@

   self

-GameObject.defaultModules
+GameObject.defaultModules =  = [Bindable, Ageable, Bounded, Clampable, Cooldown, Drawable, Expirable, Follow, Metered, Movable, Rotatable, TimedEvents, Tween]

 ###*
 Construct an object instance from the given entity data.


Move DM channel logic into client

Move DM channel logic into client

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -90,7 +90,6 @@
         return true

     send: (envelope, strings...) ->
-        @client.postMessage(str, envelope.room) for str in strings
         # Check if the target room is also a user's username
         user = @robot.brain.userForName(envelope.room)

@@ -106,24 +105,9 @@
             return

         # Otherwise, create a new DM channel ID and message it.
-        @client.getUserDirectMessageChannel user.id, (data) =>
-
-          # If there's no error, store the DM channel ID and send the message
-          unless data.error
-              user.mm.dm_channel_id = data.id
-              @client.postMessage(str, data.id) for str in strings
-              return
-
-          # Mattermost's API returns a 500 error if a DM channel already exists
-          # We can look through all the channels and find the one that only
-          # contains us and the target user and has a total of 2 members
-          for channel, details of @client.getAllChannels()
-              @client.getChannelInfo channel, 2, (info) =>
-                  return if info.member_count is not 2
-                  for member in info.members
-                      if member.username is envelope.room
-                          user.mm.dm_channel_id = info.id
-                          @client.postMessage(str, info.id) for str in strings
+        @client.getUserDirectMessageChannel user.id, (channel) =>
+            user.mm.dm_channel_id = channel.id
+            @client.postMessage(str, channel.id) for str in strings

     reply: (envelope, strings...) ->
         @robot.logger.debug "Reply"


Support room names. Fixes loafoe/hubot-matteruser#58

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -211,7 +211,7 @@
         user = @robot.brain.userForId mmPost.user_id
         user.room = mmPost.channel_id
         user.room_name = msg.data.channel_display_name
-
+
         text = mmPost.message
         if msg.data.channel_type == 'D'
           if !///^@?#{@robot.name} ///i.test(text) # Direct message
@@ -257,6 +257,11 @@

     slackAttachmentMessage: (data) =>
         return unless data.room
+
+        # Convert data.room to channel_id in case it's a room name
+        channelInfo = @client.findChannelByName(data.room)
+        if channelInfo != null
+            data.room = channelInfo.id
         msg = {}
         msg.text = data.text
         msg.type = "slack_attachment"


Name this anonymous function.

Name this anonymous function.

--- src/honeybadger.coffee
+++ src/honeybadger.coffee
@@ -46,7 +46,7 @@
     @_sendRequest(notice.toJSON())

   wrap: (func) ->
-    () ->
+    honeybadgerWrapper = () ->
       try
         func.apply(this, arguments)
       catch e


Bail out at the end of the middleware stack either way

--- src/middleware.coffee
+++ src/middleware.coffee
@@ -58,6 +58,7 @@
         try
           return fn error
         catch error
+          return

       current = self._middleware[index++]


Output which package failed

Output which package failed

--- tasks/spec-task.coffee
+++ tasks/spec-task.coffee
@@ -23,10 +23,11 @@
         opts:
           cwd: packagePath
           env: _.extend({}, process.env, ATOM_PATH: rootDir)
-      grunt.verbose.writeln("Launching #{path.basename(packagePath)} specs.")
+      grunt.verbose.writeln "Launching #{path.basename(packagePath)} specs."
       spawn options, (error, results, code) ->
-        grunt.verbose.writeln()
-        passed = passed and not error and code is 0
+        if error
+          passed = false
+          grunt.log.error "#{path.basename(packagePath)} specs failed"
         callback()

     modulesDirectory = path.resolve('node_modules')
@@ -49,13 +50,13 @@
       cmd: appPath
       args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
     spawn options, (error, results, code) ->
-      grunt.verbose.writeln()
       packageSpecQueue.concurrency = 2
-      callback(null, not error and code is 0)
+      callback(null, not error)

   grunt.registerTask 'run-specs', 'Run the specs', ->
     done = @async()
     startTime = Date.now()
+
     async.parallel [runCoreSpecs, runPackageSpecs], (error, results) ->
       [coreSpecPassed, packageSpecsPassed] = results
       elapsedTime = Math.round((Date.now() - startTime) / 100) / 10


Log empty line after specs complete

Log empty line after specs complete

--- tasks/spec-task.coffee
+++ tasks/spec-task.coffee
@@ -25,6 +25,7 @@
           env: _.extend({}, process.env, ATOM_PATH: rootDir)
       grunt.log.writeln("Launching #{path.basename(packagePath)} specs.")
       spawn options, (error, results, code) ->
+        grunt.log.writeln()
         passed = passed and code is 0
         callback()

@@ -48,6 +49,7 @@
       cmd: appPath
       args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
     spawn options, (error, results, code) ->
+      grunt.log.writeln()
       packageSpecQueue.concurrency = 2
       callback(null, code is 0)


fix computation of attribute to indicate whether table is on outermost level

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -314,7 +314,7 @@
   @_compute_pod_dimensions      me
   @_compute_table_height        me
   #.........................................................................................................
-  me._tmp_is_outermost  = layout_name_stack.length > 1
+  me._tmp_is_outermost  = layout_name_stack.length < 2
   me._tmp_name          = layout_name_stack.join '/'
   ### Preparatory ###
   yield from @_walk_opening_events                      me


Fixing issue #965 -- first character of '.' prefixed folder.

--- command.coffee
+++ command.coffee
@@ -151,7 +151,7 @@
 writeJs = (source, js, base) ->
   filename  = path.basename(source, path.extname(source)) + '.js'
   srcDir    = path.dirname source
-  baseDir   = srcDir.substring base.length
+  baseDir   = if base is '.' then srcDir else srcDir.substring base.length
   dir       = if opts.output then path.join opts.output, baseDir else srcDir
   jsPath    = path.join dir, filename
   compile   = ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/oscillator.coffee
+++ src/oscillator.coffee
@@ -1,5 +1,5 @@
 ###*
-Creates an osci
+Creates an oscillator

 By default, images are loaded asynchronously. A proxy object is
 returned immediately. Even though it has a draw method it will not


cannot rely on composition to build old deltas

--- src/undo-manager.coffee
+++ src/undo-manager.coffee
@@ -65,11 +65,11 @@
     )
     @ignoringChanges = false
     @editor.on(@editor.constructor.events.USER_TEXT_CHANGE, (delta) =>
-      if this.record(delta, @oldDelta) and not @ignoringChanges
-        @oldDelta = @oldDelta.compose(delta)
+      this.record(delta, @oldDelta) unless @ignoringChanges
+      @oldDelta = @editor.getDelta()
     ).on(@editor.constructor.events.API_TEXT_CHANGE, (delta) =>
-      if this.record(delta, @oldDelta)
-        @oldDelta = @oldDelta.compose(delta)
+      this.record(delta, @oldDelta)
+      @oldDelta = @editor.getDelta()
     )

   clear: ->


Fix bind error in resize method

Fix bind error in resize method

--- src/lib/dialog-box.coffee
+++ src/lib/dialog-box.coffee
@@ -44,7 +44,7 @@

     @options = _.extend(defaults, options)

-    _.bind(@resize, @)
+    _.bindAll(@, 'resize')
     $(window).on('resize', @resize)

     @.on('sfx:play', (soundId) =>


cleaning up and clarifying mongoose options handling

--- src/json_schema.coffee
+++ src/json_schema.coffee
@@ -7,8 +7,14 @@

 _.mixin filterValues: (obj, test) -> _.object _.filter _.pairs(obj), ([k, v]) -> test v, k

-# from: https://github.com/LearnBoost/mongoose/blob/3.8.x/lib/schematype.js
-has_non_mongoose_reserved_keys = (obj) -> _.isEmpty _.difference _.keys(obj), [
+# list from: https://github.com/LearnBoost/mongoose/blob/3.8.x/lib/schematype.js
+#
+# This is very much a hack. In mongoose, there is no way to determine if
+# an object is just a subschema or is the options object of a field.
+# The 'solution' is to see if there are any keys of the object which are
+# NOT mongoose reserved keys- if there are only mongoose reserved keys,
+# very very likely the object is just an options object
+has_only_mongoose_reserved_keys = (obj) -> _.isEmpty _.difference _.keys(obj), [
   'default', 'index', 'unique', 'required', 'auto',
   'sparse', 'select', 'set', 'get', 'type', 'ref',
   'validate', 'getDefault', 'applySetters',
@@ -87,7 +93,7 @@
       switch
         when mongoose_type_to_schema[mongoose_fragment.name]?
           mongoose_type_to_schema[mongoose_fragment.name]()
-        when mongoose_fragment.type? and has_non_mongoose_reserved_keys mongoose_fragment
+        when mongoose_fragment.type? and has_only_mongoose_reserved_keys mongoose_fragment # is options obj
           convert mongoose_fragment.type
         when _.isPlainObject mongoose_fragment
           required = _.keys _.filterValues mongoose_fragment, (subfragment) -> subfragment.required
@@ -121,10 +127,8 @@
       switch
         when _.isArray tree
           _.map tree, spec_from_tree
-        when tree.type? and tree.required? and has_non_mongoose_reserved_keys tree
-          _.pick tree, ['type', 'required']
-        when tree.type? and has_non_mongoose_reserved_keys tree
-          tree.type
+        when tree.type? and has_only_mongoose_reserved_keys tree # is options obj
+          if tree.required? then _.pick tree, ['type', 'required'] else tree.type
         when _.isPlainObject tree
           # Remove virtuals
           tree = _.filterValues tree, (subtree) -> not (subtree.getters? and subtree.setters?)


set TypeFlags correctly in login7-payload

--- src/login7-payload.coffee
+++ src/login7-payload.coffee
@@ -49,12 +49,13 @@

 TYPE_FLAGS =
   SQL_DFLT: 0x00,
-  SQL_TSQL: 0x01,
+  SQL_TSQL: 0x08,

   OLEDB_OFF: 0x00,
-  OLEDB_ON: 0x02,                 # Introduced in TDS 7.2
-
-  READ_ONLY_INTENT: 0x04          # Introduced in TDS 7.4
+  OLEDB_ON: 0x10,                 # Introduced in TDS 7.2
+
+  READ_WRITE_INTENT: 0x00,
+  READ_ONLY_INTENT: 0x20          # Introduced in TDS 7.4

 FLAGS_3 =
   CHANGE_PASSWORD_NO: 0x00,


typo in tests

typo in tests

--- test/data_tests.coffee
+++ test/data_tests.coffee
@@ -543,7 +543,7 @@

                 it "When I send a req to merge with doc with id 123", (done) ->
                     data = "new_field":"created_value"
-                    client.put 'data/merge/123/', data \
+                    client.put 'data/merge/123/', data, \
                                 (error, response, body) =>
                         @response = response
                         done()


add base test testing

add base test testing

--- test/test.coffee
+++ test/test.coffee
@@ -2,6 +2,8 @@
 path   = require 'path'
 fs     = require 'fs'
 Roots  = require 'roots'
+W      = require 'when'
+require('shelljs/global')

 test_template_path = path.resolve(_path, '../../')
 test_path          = path.join(_path, 'tmp')
@@ -42,3 +44,14 @@
     tgt = path.join(test_path, 'public', 'index.html')
     fs.existsSync(tgt).should.be.ok
     done()
+
+describe 'base', ->
+  before -> h.project.compile(Roots, 'tmp')
+
+  it 'should pass all tests', (done) ->
+    moveToTmp = cd test_path
+    mocha = W exec 'npm test'
+      .then (res) ->
+        if res.code > 0 or res.output.indexOf('failing') > -1
+          W.reject
+      .then done()


:art: Titleize Atom Community

:art: Titleize Atom Community

--- lib/about-view.coffee
+++ lib/about-view.coffee
@@ -18,7 +18,7 @@
           @span class: 'inline', ' by '
           @a class: 'icon icon-logo-github', href: 'https://github.com'
           @span class: 'inline', ' and the '
-          @a href: 'https://github.com/atom/atom/contributors', 'Atom community'
+          @a href: 'https://github.com/atom/atom/contributors', 'Atom Community'

   initialize: ({@uri}) ->
     @atomVersion.text(atom.getVersion())


Remove dispose buffer observer when editor is closed. Check that the editor is the active one to not cause problems when multiple editors are pointing to the same buffer.

--- lib/atom-react.coffee
+++ lib/atom-react.coffee
@@ -1,3 +1,5 @@
+{CompositeDisposable, Disposable} = require 'atom'
+
 contentCheckRegex = null
 defaultDetectReactFilePattern = '/((require\\([\'"]react(?:-native)?[\'"]\\)))|(import\\s+\\w+\\s+from\\s+[\'"]react(?:-native)?[\'"])/'
 autoCompleteTagStartRegex = /(<)([a-zA-Z0-9\.:$_]+)/g
@@ -191,7 +193,7 @@
             editor.setCursorBufferPosition([firstChangedLine, range[0][1]])

   autoCloseTag: (eventObj, editor) ->
-    return if not @isReactEnabledForEditor editor
+    return if not @isReactEnabledForEditor(editor) or editor != atom.workspace.getActiveTextEditor()

     if eventObj?.newText is '>' and !eventObj.oldText
       tokenizedLine = editor.displayBuffer?.tokenizedBuffer?.tokenizedLineForRow(eventObj.newRange.end.row)
@@ -286,14 +288,15 @@
     disposableBufferEvent = editor.buffer.onDidChange (e) =>
                         @autoCloseTag e, editor

-    @disposables.push(disposableBufferEvent);
+    @disposables.add editor.onDidDestroy => disposableBufferEvent.dispose()
+
+    @disposables.add(disposableBufferEvent);

   deactivate: ->
-    for disposable in @disposables
-      disposable.dispose()
+    @disposables.dispose()
   activate: ->

-    @disposables = [];
+    @disposables = new CompositeDisposable();

     jsxTagStartPattern = '(?x)((^|=|return)\\s*<([^!/?](?!.+?(</.+?>))))'
     jsxComplexAttributePattern = '(?x)\\{ [^}"\']* $|\\( [^)"\']* $'
@@ -313,10 +316,10 @@
     disposableHTMLTOJSX = atom.commands.add 'atom-workspace', 'react:HTML-to-JSX', => @onHTMLToJSX()
     disposableProcessEditor = atom.workspace.observeTextEditors @processEditor.bind(this)

-    @disposables.push disposableConfigListener
-    @disposables.push disposableReformat
-    @disposables.push disposableHTMLTOJSX
-    @disposables.push disposableProcessEditor
+    @disposables.add disposableConfigListener
+    @disposables.add disposableReformat
+    @disposables.add disposableHTMLTOJSX
+    @disposables.add disposableProcessEditor

 module.exports = AtomReact


improved readability of generator test

improved readability of generator test

--- test/generators.coffee
+++ test/generators.coffee
@@ -5,10 +5,9 @@

 # ensure that these tests are only run if generators are available
 generatorsAreAvailable = ->
-  for execArg in process.execArgv
-    if execArg.match 'harmony'
-      return 1
-  return 0
+  for execArg in process.execArgv when execArg in ['--harmony', '--harmony-generators']
+    return yes
+  no

 if generatorsAreAvailable()


+ load all types testing

+ load all types testing

--- test/index-test.coffee
+++ test/index-test.coffee
@@ -1 +1,25 @@
-require '../'+chai            = require 'chai'
+sinon           = require 'sinon'
+sinonChai       = require 'sinon-chai'
+should          = chai.should()
+expect          = chai.expect
+assert          = chai.assert
+chai.use(sinonChai)
+
+Type = require '../src'
+
+allTypes = [
+  'Number'
+  'Int'
+  'Float'
+  'String'
+  'Object'
+  'Function'
+  'Boolean'
+]
+
+describe 'TypeInfo', ->
+  it 'should load all types', ->
+    types = Object.keys Type::_objects
+    allTypes.forEach (t)->
+      expect(types).be.include t


Fixed context menu disable in lib/chart-view.coffee.

--- lib/chart-view.coffee
+++ lib/chart-view.coffee
@@ -11,8 +11,7 @@
   setModel: (@model) ->

   init: (base64Source) ->
-    $('.openmdao-chart-container')
-      .on('contextmenu', -> false)
+    @container.on('contextmenu', -> false)
     @setWebview(base64Source)

   setWebview: (base64Source) ->


:fire: call to legacy method

:fire: call to legacy method

--- lib/decoration.coffee
+++ lib/decoration.coffee
@@ -116,7 +116,6 @@
     oldProperties = @properties
     @properties = newProperties
     @properties.id = @id
-    @minimap.decorationDidChangeType(this) if newProperties.type?

     @emitter.emit 'did-change-properties', {oldProperties, newProperties}


Postfix ifs

Postfix ifs

--- src/application-delegate.coffee
+++ src/application-delegate.coffee
@@ -166,8 +166,7 @@

   onDidOpenLocations: (callback) ->
     outerCallback = (event, message, detail) ->
-      if message is 'open-locations'
-        callback(detail)
+      callback(detail) if message is 'open-locations'

     ipcRenderer.on('message', outerCallback)
     new Disposable ->
@@ -175,8 +174,7 @@

   onUpdateAvailable: (callback) ->
     outerCallback = (event, message, detail) ->
-      if message is 'did-begin-downloading-update'
-        callback(detail)
+      callback(detail) if message is 'did-begin-downloading-update'

     ipcRenderer.on('message', outerCallback)
     new Disposable ->
@@ -187,8 +185,7 @@

   onDidBeginCheckingForUpdate: (callback) ->
     outerCallback = (event, message, detail) ->
-      if message is 'checking-for-update'
-        callback(detail)
+      callback(detail) if message is 'checking-for-update'

     ipcRenderer.on('message', outerCallback)
     new Disposable ->
@@ -196,8 +193,7 @@

   onDidCompleteDownloadingUpdate: (callback) ->
     outerCallback = (event, message, detail) ->
-      if message is 'update-available'
-        callback(detail)
+      callback(detail) if message is 'update-available'

     ipcRenderer.on('message', outerCallback)
     new Disposable ->
@@ -205,13 +201,11 @@

   onUpdateNotAvailable: (callback) ->
     outerCallback = (event, message, detail) ->
-      if message is 'update-not-available'
-        callback(detail)
-
-    ipcRenderer.on('message', outerCallback)
-    new Disposable ->
-      ipcRenderer.removeListener('message', outerCallback)
-
+      callback(detail) if message is 'update-not-available'
+
+    ipcRenderer.on('message', outerCallback)
+    new Disposable ->
+      ipcRenderer.removeListener('message', outerCallback)

   onApplicationMenuCommand: (callback) ->
     outerCallback = (event, args...) ->


Restructure

Restructure

--- lib/diffs-view.coffee
+++ lib/diffs-view.coffee
@@ -13,43 +13,72 @@
   </div>
 """

+
+
+class GitHistory
+  commits: {}
+  numberCommits: 10
+
+  constructor: ->
+    @repoPromise = Git.Repository.open(atom.project.getPaths()[0])
+
+  addCommit: (commit) -> @commits[commit.sha()] = commit
+
+  getCommit: (sha) -> @commits[sha]
+
+  walkHistory: (commitCallback) ->
+    walk = (repo, walker, numberCommits, callback) ->
+      return if numberCommits == 0
+      walker.next().then (oid) =>
+        repo.getCommit(oid).then(callback)
+        walk(repo, walker, numberCommits - 1, callback)
+
+    @repoPromise.then (repo) =>
+      repo.getHeadCommit().then (commit) =>
+        walker = repo.createRevWalk()
+        walker.push(commit.id())
+        walk repo, walker, @numberCommits, (commit) =>
+          @addCommit(commit)
+          commitCallback(commit)
+
+
+
+class TemplateHelper
+  @addTemplate: (parent, htmlString) ->
+    template = document.createElement('template')
+    template.innerHTML = htmlString
+    parent.appendChild(template)
+    template
+
+  @renderTemplate: (template) ->
+    document.importNode(template.content, true)
+
+
+
 class DiffsView extends HTMLElement
   createdCallback: ->
     window.diffsView = this
+
+    @history = new GitHistory()

     @innerHTML = BaseTemplate
     @historyNode = @querySelector('.history')
     @diffsNode = @querySelector('.diffs')

-    @commitTemplate = @addTemplate(CommitTemplateString)
+    @commitTemplate = TemplateHelper.addTemplate(this, CommitTemplateString)

-    Git.Repository.open(atom.project.getPaths()[0])
-      .then (@repo) =>
-        console.log 'repo', @repo
-        @repo.getCurrentBranch().then (@branch) =>
-          console.log 'branch', @branch
-        @renderHistory()
-      .catch (error) ->
-        console.log 'error', error
-        console.log error.stack
+    @renderHistory().catch (error) ->
+      console.error error.message, error
+      console.error error.stack

   getTitle: ->
     'Yeah, view that diff!'
-
-  addTemplate: (htmlString) ->
-    template = document.createElement('template')
-    template.innerHTML = htmlString
-    @appendChild(template)
-    template
-
-  renderTemplate: (template) ->
-    document.importNode(template.content, true)

   renderCommit: (commit) ->
     console.log 'commit', commit.sha(), commit.author().name(), commit.message()

     # We still dont have a good view framework yet
-    commitNode = @renderTemplate(@commitTemplate)
+    commitNode = TemplateHelper.renderTemplate(@commitTemplate)
     commitNode.querySelector('.sha').textContent = commit.sha()
     commitNode.querySelector('.author').textContent = commit.author().name()
     commitNode.querySelector('.message').textContent = commit.message().split('\n')[0]
@@ -57,19 +86,6 @@
     @historyNode.appendChild(commitNode)

   renderHistory: ->
-    @walkHistory (commit) => @renderCommit(commit)
-
-  walkHistory: (commitCallback) ->
-    numberCommits=10
-    @repo.getHeadCommit().then (commit) =>
-      walker = @repo.createRevWalk()
-      walker.push(commit.id())
-      @walk(walker, numberCommits, commitCallback)
-
-  walk: (walker, numberCommits, callback) ->
-    return if numberCommits == 0
-    walker.next().then (oid) =>
-      @repo.getCommit(oid).then(callback)
-      @walk(walker, numberCommits - 1, callback)
+    @history.walkHistory (commit) => @renderCommit(commit)

 module.exports = document.registerElement 'git-experiment-diffs-view', prototype: DiffsView.prototype


feat(highlights): encode HTML entities. (#242)

--- lib/highlights.coffee
+++ lib/highlights.coffee
@@ -60,6 +60,7 @@
         value = ' ' unless value
         scopes = scopes.map (scope) -> "syntax--#{scope.replace(/\./g, '.syntax--')}"
         html = updateScopeStack scopeStack, scopes, html
+        value = value.replace(/&/, '&').replace(/</, '<')
         html += "<span>#{value}</span>"
       html = popScope(scopeStack, html) while scopeStack.length > 0
       html += '\n'


tweak, fix for occasional read issue

tweak, fix for occasional read issue

--- lib/misc/paths.coffee
+++ lib/misc/paths.coffee
@@ -1,5 +1,3 @@
-# TODO: working directory dialog
-
 path =  require 'path'
 fs =    require 'fs'
 child_process = require 'child_process'
@@ -52,7 +50,7 @@
     new Promise (resolve, reject) =>
       proc = child_process.spawn @jlpath(), ["--version"]
       proc.on 'exit', () =>
-        str = proc.stdout.read().toString()
+        str = proc.stdout.read()?.toString()
         res = str.match /(\d+)\.(\d+)\.(\d+)/
         return reject() unless res?
         [_, major, minor, patch] = res


Silence React event warning by removing meaningless return value

--- src/core/LiterallyCanvas.coffee
+++ src/core/LiterallyCanvas.coffee
@@ -76,6 +76,9 @@

   trigger: (name, data) ->
     @canvas.dispatchEvent(new CustomEvent(name, detail: data))
+    # dispatchEvent has a boolean value that doesn't mean anything to us, so
+    # don't let CoffeeScript send it back
+    null

   on: (name, fn) ->
     wrapper = (e) -> fn e.detail


Improved specs

Improved specs

--- src/spec/index.coffee
+++ src/spec/index.coffee
@@ -5,11 +5,9 @@
         spyOn app, 'onDeviceReady'
         app.initialize()
         helper.trigger window.document, 'deviceready'
-        return
-
-      waitsFor (->
+      waitsFor ->
         app.onDeviceReady.calls.length > 0
-      ), 'onDeviceReady should be called once', 500
+      , 'onDeviceReady should be called once', 500
       runs ->
         expect(app.onDeviceReady).toHaveBeenCalled()


Add gui control for PlayState#ended to test replays.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -51,6 +51,7 @@
           @background.debugging = @platforms.debugging = @player.debugging = @debugging
           @debug.reset() unless @debugging
         @gui.add(@, 'detachedCamera').onFinishChange => @_toggleCameraAttachment()
+        @gui.add(@, 'ended')
         @gui.addOpenFolder('gravity').addRange @physics.arcade.gravity, 'y'

       # First:


Case insentive resolution prefixer

Case insentive resolution prefixer

--- lib/resolution.coffee
+++ lib/resolution.coffee
@@ -3,8 +3,8 @@

 n2f = require('num2fraction')

-regexp = /(min|max)-resolution\s*:\s*\d*\.?\d+(dppx|dpi)/g
-split  = /(min|max)-resolution(\s*:\s*)(\d*\.?\d+)(dppx|dpi)/
+regexp = /(min|max)-resolution\s*:\s*\d*\.?\d+(dppx|dpi)/gi
+split  = /(min|max)-resolution(\s*:\s*)(\d*\.?\d+)(dppx|dpi)/i

 class Resolution extends Prefixer


Fix condition

Fix condition

--- lib/sshfs-node.coffee
+++ lib/sshfs-node.coffee
@@ -49,7 +49,7 @@
     portOption = util.format '-o port=%d', options.port

   pathOption = '/'
-  if options && (typeof options.path === 'string')
+  if options && (typeof(options.path) == 'string')
     pathOption = util.format '%s', options.path

   # sshfs -o IdentityFile=~/.ssh/id_rsa user@localhost:"/" "~/mnt/localhost_mount/"


Fix for Issue #66

Fix for Issue #66

--- lib/transpiler.coffee
+++ lib/transpiler.coffee
@@ -32,11 +32,12 @@
     config = @getConfig()
     pathTo = @getPaths sourceFile, config

-    localConfig = @getLocalConfig pathTo.sourceFileDir, pathTo.projectPath, {}
-    # merge local configs with global. local wins
-    merge config, localConfig
-    # recalc paths
-    pathTo = @getPaths sourceFile, config
+    if config.allowLocalOverride
+      localConfig = @getLocalConfig pathTo.sourceFileDir, pathTo.projectPath, {}
+      # merge local configs with global. local wins
+      merge config, localConfig
+      # recalc paths
+      pathTo = @getPaths sourceFile, config

     return if config.transpileOnSave isnt true

@@ -171,6 +172,8 @@
         merge  jsonContent, localConfig
         localConfig = jsonContent
     if fromDir isnt toDir
+      # stop infinite recursion https://github.com/gandm/language-babel/issues/66
+      if fromDir == path.dirname(fromDir) then return localConfig
       return @getLocalConfig path.dirname(fromDir), toDir, localConfig
     else return localConfig

@@ -212,6 +215,8 @@
     if fs.existsSync babelrcFile
       return true
     if fromDir isnt toDir
+      # stop infinite recursion https://github.com/gandm/language-babel/issues/66
+      if fromDir == path.dirname(fromDir) then return false
       return @isBabelrcInPath path.dirname(fromDir), toDir
     else return false


Reorganize PackageManager into sections

Reorganize PackageManager into sections

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -48,7 +48,7 @@
   Section: Event Subscription
   ###

-  # Essential: Invoke the given callback when all packages have been activated.
+  # Public: Invoke the given callback when all packages have been activated.
   #
   # * `callback` {Function}
   #
@@ -56,7 +56,7 @@
   onDidLoadAll: (callback) ->
     @emitter.on 'did-load-all', callback

-  # Essential: Invoke the given callback when all packages have been activated.
+  # Public: Invoke the given callback when all packages have been activated.
   #
   # * `callback` {Function}
   #
@@ -75,10 +75,10 @@
     EmitterMixin::on.apply(this, arguments)

   ###
-  Section: Instance Methods
-  ###
-
-  # Extended: Get the path to the apm command.
+  Section: Package system data
+  ###
+
+  # Public: Get the path to the apm command.
   #
   # Return a {String} file path to apm.
   getApmPath: ->
@@ -86,19 +86,43 @@
     commandName += '.cmd' if process.platform is 'win32'
     @apmPath ?= path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager', 'bin', commandName)

-  # Extended: Get the paths being used to look for packages.
+  # Public: Get the paths being used to look for packages.
   #
   # Returns an {Array} of {String} directory paths.
   getPackageDirPaths: ->
     _.clone(@packageDirPaths)

-  getPackageState: (name) ->
-    @packageStates[name]
-
-  setPackageState: (name, state) ->
-    @packageStates[name] = state
-
-  # Extended: Enable the package with the given name.
+  ###
+  Section: General package data
+  ###
+
+  # Public: Resolve the given package name to a path on disk.
+  #
+  # * `name` - The {String} package name.
+  #
+  # Return a {String} folder path or undefined if it could not be resolved.
+  resolvePackagePath: (name) ->
+    return name if fs.isDirectorySync(name)
+
+    packagePath = fs.resolve(@packageDirPaths..., name)
+    return packagePath if fs.isDirectorySync(packagePath)
+
+    packagePath = path.join(@resourcePath, 'node_modules', name)
+    return packagePath if @hasAtomEngine(packagePath)
+
+  # Public: Is the package with the given name bundled with Atom?
+  #
+  # * `name` - The {String} package name.
+  #
+  # Returns a {Boolean}.
+  isBundledPackage: (name) ->
+    @getPackageDependencies().hasOwnProperty(name)
+
+  ###
+  Section: Enabling and disabling packages
+  ###
+
+  # Public: Enable the package with the given name.
   #
   # Returns the {Package} that was enabled or null if it isn't loaded.
   enablePackage: (name) ->
@@ -106,7 +130,7 @@
     pack?.enable()
     pack

-  # Extended: Disable the package with the given name.
+  # Public: Disable the package with the given name.
   #
   # Returns the {Package} that was disabled or null if it isn't loaded.
   disablePackage: (name) ->
@@ -114,51 +138,23 @@
     pack?.disable()
     pack

-  # Activate all the packages that should be activated.
-  activate: ->
-    for [activator, types] in @packageActivators
-      packages = @getLoadedPackagesForTypes(types)
-      activator.activatePackages(packages)
-    @emit 'activated'
-    @emitter.emit 'did-activate-all'
-
-  # another type of package manager can handle other package types.
-  # See ThemeManager
-  registerPackageActivator: (activator, types) ->
-    @packageActivators.push([activator, types])
-
-  activatePackages: (packages) ->
-    @activatePackage(pack.name) for pack in packages
-    @observeDisabledPackages()
-
-  # Activate a single package by name
-  activatePackage: (name) ->
-    if pack = @getActivePackage(name)
-      Q(pack)
-    else
-      pack = @loadPackage(name)
-      pack.activate().then =>
-        @activePackages[pack.name] = pack
-        pack
-
-  # Deactivate all packages
-  deactivatePackages: ->
-    @deactivatePackage(pack.name) for pack in @getLoadedPackages()
-    @unobserveDisabledPackages()
-
-  # Deactivate the package with the given name
-  deactivatePackage: (name) ->
-    pack = @getLoadedPackage(name)
-    if @isPackageActive(name)
-      @setPackageState(pack.name, state) if state = pack.serialize?()
-    pack.deactivate()
-    delete @activePackages[pack.name]
-
-  # Essential: Get an {Array} of all the active {Package}s.
+  # Public: Is the package with the given name disabled?
+  #
+  # * `name` - The {String} package name.
+  #
+  # Returns a {Boolean}.
+  isPackageDisabled: (name) ->
+    _.include(atom.config.get('core.disabledPackages') ? [], name)
+
+  ###
+  Section: Accessing active packages
+  ###
+
+  # Public: Get an {Array} of all the active {Package}s.
   getActivePackages: ->
     _.values(@activePackages)

-  # Essential: Get the active {Package} with the given name.
+  # Public: Get the active {Package} with the given name.
   #
   # * `name` - The {String} package name.
   #
@@ -173,6 +169,91 @@
   # Returns a {Boolean}.
   isPackageActive: (name) ->
     @getActivePackage(name)?
+
+  ###
+  Section: Accessing loaded packages
+  ###
+
+  # Public: Get an {Array} of all the loaded {Package}s
+  getLoadedPackages: ->
+    _.values(@loadedPackages)
+
+  # Get packages for a certain package type
+  #
+  # * `types` an {Array} of {String}s like ['atom', 'textmate'].
+  getLoadedPackagesForTypes: (types) ->
+    pack for pack in @getLoadedPackages() when pack.getType() in types
+
+  # Public: Get the loaded {Package} with the given name.
+  #
+  # * `name` - The {String} package name.
+  #
+  # Returns a {Package} or undefined.
+  getLoadedPackage: (name) ->
+    @loadedPackages[name]
+
+  # Public: Is the package with the given name loaded?
+  #
+  # * `name` - The {String} package name.
+  #
+  # Returns a {Boolean}.
+  isPackageLoaded: (name) ->
+    @getLoadedPackage(name)?
+
+  ###
+  Section: Accessing available packages
+  ###
+
+  # Public: Get an {Array} of {String}s of all the available package paths.
+  getAvailablePackagePaths: ->
+    packagePaths = []
+
+    for packageDirPath in @packageDirPaths
+      for packagePath in fs.listSync(packageDirPath)
+        packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
+
+    packagesPath = path.join(@resourcePath, 'node_modules')
+    for packageName, packageVersion of @getPackageDependencies()
+      packagePath = path.join(packagesPath, packageName)
+      packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
+
+    _.uniq(packagePaths)
+
+  # Public: Get an {Array} of {String}s of all the available package names.
+  getAvailablePackageNames: ->
+    _.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
+
+  # Public: Get an {Array} of {String}s of all the available package metadata.
+  getAvailablePackageMetadata: ->
+    packages = []
+    for packagePath in @getAvailablePackagePaths()
+      name = path.basename(packagePath)
+      metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
+      packages.push(metadata)
+    packages
+
+  ###
+  Section: Private
+  ###
+
+  getPackageState: (name) ->
+    @packageStates[name]
+
+  setPackageState: (name, state) ->
+    @packageStates[name] = state
+
+  getPackageDependencies: ->
+    unless @packageDependencies?
+      try
+        metadataPath = path.join(@resourcePath, 'package.json')
+        {@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
+      @packageDependencies ?= {}
+
+    @packageDependencies
+
+  hasAtomEngine: (packagePath) ->
+    metadata = Package.loadMetadata(packagePath, true)
+    metadata?.engines?.atom?

   unobserveDisabledPackages: ->
     @disabledPackagesSubscription?.off()
@@ -234,99 +315,42 @@
     else
       throw new Error("No loaded package for name '#{name}'")

-  # Essential: Get the loaded {Package} with the given name.
-  #
-  # * `name` - The {String} package name.
-  #
-  # Returns a {Package} or undefined.
-  getLoadedPackage: (name) ->
-    @loadedPackages[name]
-
-  # Essential: Is the package with the given name loaded?
-  #
-  # * `name` - The {String} package name.
-  #
-  # Returns a {Boolean}.
-  isPackageLoaded: (name) ->
-    @getLoadedPackage(name)?
-
-  # Essential: Get an {Array} of all the loaded {Package}s
-  getLoadedPackages: ->
-    _.values(@loadedPackages)
-
-  # Get packages for a certain package type
-  #
-  # * `types` an {Array} of {String}s like ['atom', 'textmate'].
-  getLoadedPackagesForTypes: (types) ->
-    pack for pack in @getLoadedPackages() when pack.getType() in types
-
-  # Extended: Resolve the given package name to a path on disk.
-  #
-  # * `name` - The {String} package name.
-  #
-  # Return a {String} folder path or undefined if it could not be resolved.
-  resolvePackagePath: (name) ->
-    return name if fs.isDirectorySync(name)
-
-    packagePath = fs.resolve(@packageDirPaths..., name)
-    return packagePath if fs.isDirectorySync(packagePath)
-
-    packagePath = path.join(@resourcePath, 'node_modules', name)
-    return packagePath if @hasAtomEngine(packagePath)
-
-  # Essential: Is the package with the given name disabled?
-  #
-  # * `name` - The {String} package name.
-  #
-  # Returns a {Boolean}.
-  isPackageDisabled: (name) ->
-    _.include(atom.config.get('core.disabledPackages') ? [], name)
-
-  hasAtomEngine: (packagePath) ->
-    metadata = Package.loadMetadata(packagePath, true)
-    metadata?.engines?.atom?
-
-  # Extended: Is the package with the given name bundled with Atom?
-  #
-  # * `name` - The {String} package name.
-  #
-  # Returns a {Boolean}.
-  isBundledPackage: (name) ->
-    @getPackageDependencies().hasOwnProperty(name)
-
-  getPackageDependencies: ->
-    unless @packageDependencies?
-      try
-        metadataPath = path.join(@resourcePath, 'package.json')
-        {@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
-      @packageDependencies ?= {}
-
-    @packageDependencies
-
-  # Extended: Get an {Array} of {String}s of all the available package paths.
-  getAvailablePackagePaths: ->
-    packagePaths = []
-
-    for packageDirPath in @packageDirPaths
-      for packagePath in fs.listSync(packageDirPath)
-        packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
-
-    packagesPath = path.join(@resourcePath, 'node_modules')
-    for packageName, packageVersion of @getPackageDependencies()
-      packagePath = path.join(packagesPath, packageName)
-      packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
-
-    _.uniq(packagePaths)
-
-  # Extended: Get an {Array} of {String}s of all the available package names.
-  getAvailablePackageNames: ->
-    _.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
-
-  # Extended: Get an {Array} of {String}s of all the available package metadata.
-  getAvailablePackageMetadata: ->
-    packages = []
-    for packagePath in @getAvailablePackagePaths()
-      name = path.basename(packagePath)
-      metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
-      packages.push(metadata)
-    packages
+  # Activate all the packages that should be activated.
+  activate: ->
+    for [activator, types] in @packageActivators
+      packages = @getLoadedPackagesForTypes(types)
+      activator.activatePackages(packages)
+    @emit 'activated'
+    @emitter.emit 'did-activate-all'
+
+  # another type of package manager can handle other package types.
+  # See ThemeManager
+  registerPackageActivator: (activator, types) ->
+    @packageActivators.push([activator, types])
+
+  activatePackages: (packages) ->
+    @activatePackage(pack.name) for pack in packages
+    @observeDisabledPackages()
+
+  # Activate a single package by name
+  activatePackage: (name) ->
+    if pack = @getActivePackage(name)
+      Q(pack)
+    else
+      pack = @loadPackage(name)
+      pack.activate().then =>
+        @activePackages[pack.name] = pack
+        pack
+
+  # Deactivate all packages
+  deactivatePackages: ->
+    @deactivatePackage(pack.name) for pack in @getLoadedPackages()
+    @unobserveDisabledPackages()
+
+  # Deactivate the package with the given name
+  deactivatePackage: (name) ->
+    pack = @getLoadedPackage(name)
+    if @isPackageActive(name)
+      @setPackageState(pack.name, state) if state = pack.serialize?()
+    pack.deactivate()
+    delete @activePackages[pack.name]


WatchView: Make linter happy

WatchView: Make linter happy

--- lib/watch-view.coffee
+++ lib/watch-view.coffee
@@ -59,7 +59,7 @@
         @nextButton = document.createElement('button')
         @nextButton.classList.add('btn', 'btn-xs', 'icon', 'icon-chevron-right', 'next-btn')
         @nextButton.onclick = =>
-            if @currentHistory.pos != @currentHistory.length - 1 and @currentHistory.pos?
+            if @currentHistory.pos isnt @currentHistory.length - 1 and @currentHistory.pos?
                 @currentHistory.pos += 1
                 @counter.innerText = "#{@currentHistory.pos+1} / #{@currentHistory.length}"
                 @scrollbar.scrollLeft = @currentHistory.pos * (@scrollbar.offsetWidth+1)
@@ -69,7 +69,7 @@
         @prevButton = document.createElement('button')
         @prevButton.classList.add('btn', 'btn-xs', 'icon', 'icon-chevron-left')
         @prevButton.onclick = =>
-            if @currentHistory.pos != 0 and @currentHistory.pos?
+            if @currentHistory.pos isnt 0 and @currentHistory.pos?
                 @currentHistory.pos -= 1
                 @counter.innerText = "#{@currentHistory.pos+1} / #{@currentHistory.length}"
                 @scrollbar.scrollLeft = @currentHistory.pos * (@scrollbar.offsetWidth+1)
@@ -86,14 +86,14 @@
     run: ->
         code = @getCode()
         @clearResults()
-        console.log "watchview running:", code
+        console.log 'watchview running:', code
         if code? and code.length? and code.length > 0
             @kernel.executeWatch code, (result) =>
-                console.log "watchview got result:", result
+                console.log 'watchview got result:', result
                 @resultView.addResult(result)
                 @addToHistory result

-    setCode: (code)->
+    setCode: (code) ->
         @inputEditor.setText code
         this


Unsubscribe from buffer when destroyed

Unsubscribe from buffer when destroyed

This ensures the view's subscriptions aren't holding any references
to buffers once they are destroyed.

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -23,6 +23,9 @@

     @subscribe editor, 'destroyed', ->
       bufferSavedSubscription.off()
+
+    @subscribe buffer, 'destroyed', =>
+      @unsubscribe(buffer)

   removeTrailingWhitespace: (editor, grammarScopeName) ->
     buffer = editor.getBuffer()


Use Editor::transact

Use Editor::transact

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -6,7 +6,7 @@
   activate: ->
     @editorSubscription = atom.workspace.eachEditor (editor) ->
       bufferSubscription = editor.getBuffer().on 'will-be-saved', (buffer) ->
-        buffer.transact ->
+        editor.transact ->
           if atom.config.get('whitespace.removeTrailingWhitespace')
             removeTrailingWhitespace(editor)


test omission

test omission

--- src/protomixin.spec.coffee
+++ src/protomixin.spec.coffee
@@ -2,6 +2,7 @@

   mixit = require './index'
   Mixin = require './mixinfactory'
+  UTIL = require './util'
   {beforeOnce, _, MIXINS} = require './util/spec'

   beforeOnce ->
@@ -59,13 +60,21 @@
     expect(mixin.post_protomixin).toHaveBeenCalledWith(['arg1', 'arg2'])
     expect(mixin.post_protomixin.calls.count()).toBe 2

-    it 'should omit mixin keys', ->
+  it 'should omit mixin keys', ->
       mixin = MIXINS.default_protomixin()

       class Example
-        @mixinto_proto mixin, omit: ['bar', 'baz']
+        @mixinto_proto mixin, omit: ['bar']

       e = new Example

       expect(e.bar).toBeUndefined()
-      expect(e.baz).toBeUndefined()
+      expect(e.baz).toBeDefined()
+
+  it 'should not omit all mixin keys', ->
+      mixin = MIXINS.default_protomixin()
+
+      expect(->
+        class Example
+          @mixinto_proto mixin, omit: ['bar', 'baz']
+      ).toThrow new UTIL.ArgumentError "Found nothing to mix in!"


Use class instead of id for pane container

--- src/pane-container.coffee
+++ src/pane-container.coffee
@@ -16,7 +16,7 @@
     container

   @content: ->
-    @div id: 'panes'
+    @div class: 'panes'

   initialize: (state) ->
     @destroyedItemStates = []


Fix highlighting for substitutions using braces as delimiters. The end regex was matching on the open brace in the lookahead.


Add support for GNU built-in comparison names

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -271,20 +271,29 @@

 				(?:
 					# One-character built-in comparison name
-					([note])         # 4: constant.language.comparison-name.builtin.$4.roff
+					([note])         # 4: constant.language.builtin-comparison.$4.roff
+
+					|
+
+					# GNU extensions
+					([cdfmrs])       # 5: constant.language.builtin-comparison.$5.gnu.roff
+
+					# Name being validated
+					[ \t]*
+					((?:[^ \\t\\\\]|\\\\(?!["#]).)+) # 6: Include “#escapes”

 					|

 					# Arithmetic
-					(                # 5: meta.equation.roff
+					(                # 7: meta.equation.roff

 						# Starts with a bracket
-						(\\()        # 6: punctuation.definition.begin.roff
-						(.*?)        # 7: Include “#arithmetic”
-						(\\))        # 8: punctuation.definition.end.roff
+						(\\()        # 8:  punctuation.definition.begin.roff
+						(.*?)        # 9:  Include “#arithmetic”
+						(\\))        # 10: punctuation.definition.end.roff

 						# Anything else affixed to it
-						(            # 9: Include “#arithmetic”
+						(            # 11: Include “#arithmetic”
 							(?:
 								[^\\s\\(] | # Operators/numbers
 								\\(.*?\\)   # More brackets
@@ -296,17 +305,17 @@
 						# Doesn’t start with a bracket
 						(?:
 							# Starts with a long-form string/register
-							(\\\\+[n*]\\(\\S{2}) # 10: Include “#escapes”
+							(\\\\+[n*]\\(\\S{2}) # 12: Include “#escapes”
 							|
 							# Starts with a digit or backslash
 							(?=\\d|\\\\)
 						)
-						([^\\s\\(]*) # 11: Sandwiched mathematical junk
+						([^\\s\\(]*) # 13: Sandwiched mathematical junk

 						(?: # Possible embedded brackets
-							(\\()    # 12: punctuation.definition.begin.roff
-							(.*?)    # 13: Include “#arithmetic”
-							(\\))    # 14: punctuation.definition.end.roff
+							(\\()    # 14: punctuation.definition.begin.roff
+							(.*?)    # 15: Include “#arithmetic”
+							(\\))    # 16: punctuation.definition.end.roff
 						)?
 						(?: # Possible trailing digits/operators
 							[^\\s\\(]*?
@@ -334,23 +343,23 @@
 					|

 					# String/variable comparison
-					([^\\d\\s\\\\])  # 15: punctuation.definition.string.begin.roff
-					(                # 16: variable.parameter.operand.left.roff
-						(.*?)        # 17: Include “#escapes”
+					([^\\d\\s\\\\])  # 17: punctuation.definition.string.begin.roff
+					(                # 18: variable.parameter.operand.left.roff
+						(.*?)        # 19: Include “#escapes”
 					)
-					(\\15)           # 18: punctuation.definition.string.roff
-					(                # 19: variable.parameter.operand.right.roff
-						(.*?)        # 20: Include “#escapes”
+					(\\17)           # 20: punctuation.definition.string.roff
+					(                # 21: variable.parameter.operand.right.roff
+						(.*?)        # 22: Include “#escapes”
 					)
-					(\\15)           # 21: punctuation.definition.string.end.roff
+					(\\17)           # 23: punctuation.definition.string.end.roff

 					|

 					# Anything not recognised
-					(\\S)            # 22: meta.operand.single.roff
+					(\\S)            # 24: meta.operand.single.roff
 				)?

-				(.*)                 # 23: Include “#conditional-innards”
+				(.*)                 # 25: Include “#conditional-innards”
 			"""
 			end: "$"
 			beginCaptures:
@@ -359,25 +368,27 @@
 				2:  {name: "keyword.control.roff"}
 				3:  {name: "keyword.operator.logical"}
 				4:  {name: "constant.language.builtin-comparison.$4.roff"}
-				5:  {name: "meta.equation.roff"}
-				6:  {name: "punctuation.definition.begin.roff"}
-				7:  {patterns: [{include: "#arithmetic"}]}
-				8:  {name: "punctuation.definition.end.roff"}
+				5:  {name: "constant.language.builtin-comparison.$5.gnu.roff"}
+				6:  {patterns: [include: "#escapes"]}
+				7:  {name: "meta.equation.roff"}
+				8:  {name: "punctuation.definition.begin.roff"}
 				9:  {patterns: [{include: "#arithmetic"}]}
-				10: {patterns: [{include: "#escapes"}]}
+				10: {name: "punctuation.definition.end.roff"}
 				11: {patterns: [{include: "#arithmetic"}]}
-				12: {name: "punctuation.definition.begin.roff"}
+				12: {patterns: [{include: "#escapes"}]}
 				13: {patterns: [{include: "#arithmetic"}]}
-				14: {name: "punctuation.definition.end.roff"}
-				15: {name: "punctuation.definition.string.begin.roff"}
-				16: {name: "variable.parameter.operand.left.roff"}
-				17: {patterns: [{include: "#escapes"}]}
-				18: {name: "punctuation.definition.string.roff"}
-				19: {name: "variable.parameter.operand.right.roff"}
-				20: {patterns: [{include: "#escapes"}]}
-				21: {name: "punctuation.definition.string.end.roff"}
-				22: {name: "meta.operand.single.roff"}
-				23: {patterns: [{include: "#conditional-innards"}]}
+				14: {name: "punctuation.definition.begin.roff"}
+				15: {patterns: [{include: "#arithmetic"}]}
+				16: {name: "punctuation.definition.end.roff"}
+				17: {name: "punctuation.definition.string.begin.roff"}
+				18: {name: "variable.parameter.operand.left.roff"}
+				19: {patterns: [{include: "#escapes"}]}
+				20: {name: "punctuation.definition.string.roff"}
+				21: {name: "variable.parameter.operand.right.roff"}
+				22: {patterns: [{include: "#escapes"}]}
+				23: {name: "punctuation.definition.string.end.roff"}
+				24: {name: "meta.operand.single.roff"}
+				25: {patterns: [{include: "#conditional-innards"}]}
 		},{

 			# Conditional: Else


Fix scoping and tokenisation of `.cu`/`.ul` requests

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -655,7 +655,7 @@
 		patterns: [{

 			# .ul/.cu 0: Empty request/noop
-			name: "meta.request.$3.roff"
+			name: "meta.function.request.$3.roff"
 			match: "(?:^|\\G)([.'])[ \\t]*(?:(do)[ \\t]+)?(ul|cu)\\s*(0+)(?:(?!\\\\E?\")\\D)*(?=\\s|$)(.*)$"
 			captures:
 				1: name: "punctuation.definition.function.request.roff"
@@ -667,10 +667,10 @@
 		}, {

 			# Underline following line
-			name: "meta.request.$3.roff"
 			begin: "(?:^|\\G)([.'])[ \\t]*(?:(do)[ \\t]+)?(ul|cu)(?=\\s|$|\\\\)(.*?)$(?!R)\\R?"
 			end:   "(?!\\G)(?<!\\\\)$"
 			beginCaptures:
+				0: name: "meta.function.request.$3.roff"
 				1: name: "punctuation.definition.function.request.roff"
 				2: name: "entity.function.name.roff"
 				3: name: "entity.function.name.roff"


Extend list of recognised filetypes

Extend list of recognised filetypes

--- grammars/viml.cson
+++ grammars/viml.cson
@@ -1,6 +1,12 @@
 name: "VimL"
 scopeName: "source.viml"
-fileTypes: ["vim", "vimrc", "gvimrc"]
+fileTypes: [
+	"vim"
+	"vimrc"
+	"gvimrc"
+	"nvimrc"
+	"_vimrc"
+]
 firstLineMatch: """(?x)

 	# Hashbang


Fixed bug where daily reports were not being generated to the correct channel

--- src/scripts/payroll.coffee
+++ src/scripts/payroll.coffee
@@ -17,6 +17,46 @@
   Organization = require('../models/organization').get()
   # Weeks ‘start’ on Sunday morning.

+  dailyReport = (reports) ->
+    PAYROLL = HEADERS.payrollreports
+    response = "DAILY WORK LOG: #{yesterday.format('dddd MMMM D YYYY').toUpperCase()}\n"
+    logBuffer = ''
+    offBuffer = ''
+
+    for report in reports
+      recorded = false
+      if report[PAYROLL.logged] > 0
+        status = "#{report.extra.slack}:\t#{report[PAYROLL.logged]} hours"
+        notes = report.extra.notes?.replace('\n', '; ')
+        if notes
+          status += " \"#{notes}\""
+        projectStr = ''
+        if report.extra.projects? and report.extra.projects?.length > 0
+          for project in report.extra.projects
+            projectStr += "##{project.name} "
+        if projectStr
+          projectStr = projectStr.trim()
+          status += " #{projectStr}"
+        status += "\n"
+        logBuffer += "#{status}"
+        recorded = true
+      if report[PAYROLL.vacation] > 0
+        offBuffer += "#{report.extra.slack}:\t#{report[PAYROLL.vacation]} hours unpaid"
+        recorded = true
+      if report[PAYROLL.sick] > 0
+        offBuffer += "#{report.extra.slack}:\t#{report[PAYROLL.sick]} hours unpaid"
+        recorded = true
+      if report[PAYROLL.unpaid] > 0
+        offBuffer += "#{report.extra.slack}:\t#{report[PAYROLL.unpaid]} hours unpaid"
+        recorded = true
+      if not recorded
+        offBuffer += "#{report.extra.slack}:\t0 hours\n"
+    response += logBuffer
+    if offBuffer.length > 0
+      response += "DAILY OFF-TIME LOG: #{yesterday.format('dddd MMMM D YYYY').toUpperCase()}\n"
+      response += offBuffer
+    return response
+
   generateDailyReportJob = schedule.scheduleJob '0 6 * * *', ->
     if not Organization.ready()
       Logger.warn "Don\'t make scheduled daily report,
@@ -31,46 +71,9 @@
       .done(
         (reports) ->
           numberDone = reports.length
-          PAYROLL = HEADERS.payrollreports
-          response = "DAILY WORK LOG: #{yesterday.format('dddd MMMM D YYYY').toUpperCase()}\n"
-          logBuffer = ''
-          offBuffer = ''
-
-          for report in reports
-            recorded = false
-            if report[PAYROLL.logged] > 0
-              status = "#{report.extra.slack}:\t#{report[PAYROLL.logged]} hours"
-              notes = report.extra.notes?.replace('\n', '; ')
-              if notes
-                status += " \"#{notes}\""
-              projectStr = ''
-              if report.extra.projects? and report.extra.projects?.length > 0
-                for project in report.extra.projects
-                  projectStr += "##{project.name} "
-              if projectStr
-                projectStr = projectStr.trim()
-                status += " #{projectStr}"
-              status += "\n"
-              logBuffer += "#{status}"
-              recorded = true
-            if report[PAYROLL.vacation] > 0
-              offBuffer += "#{report.extra.slack}:\t#{report[PAYROLL.vacation]} hours unpaid"
-              recorded = true
-            if report[PAYROLL.sick] > 0
-              offBuffer += "#{report.extra.slack}:\t#{report[PAYROLL.sick]} hours unpaid"
-              recorded = true
-            if report[PAYROLL.unpaid] > 0
-              offBuffer += "#{report.extra.slack}:\t#{report[PAYROLL.unpaid]} hours unpaid"
-              recorded = true
-            if not recorded
-              offBuffer += "#{report.extra.slack}:\t0 hours\n"
-
-          response += logBuffer
-          if offBuffer.length > 0
-            response += "DAILY OFF-TIME LOG: #{yesterday.format('dddd MMMM D YYYY').toUpperCase()}\n"
-            response += offBuffer
-          Logger.logToChannel response,
-                              'general'
+          report = dailyReport reports
+          Logger.logToChannel report,
+                              'bizness-time'
           Logger.logToChannel "Daily report generated for
                                #{numberDone} employees",
                               'ibizan-diagnostics'


Tokenizer: strings refactoring part 1

Tokenizer: strings refactoring part 1

--- tokenizer.coffee
+++ tokenizer.coffee
@@ -82,11 +82,8 @@
 tokenizer.parser_list.push (new Token_parser 'arrow_function', /^[-=]>/)
 # Version from the CoffeeScript source code: /^###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/
 tokenizer.parser_list.push (new Token_parser 'comment', /^(###[^#][^]*###|#.*)/)
-tokenizer.parser_list.push (new Token_parser 'string_literal', ///
-  ^('''|')
-  # (?![^]*[^\\]\1[^]*\1)     # ensures that all corresponding quotes inside the string are escaped
-  (?:
-    [^\\] |
+
+string_regex_craft = ///
     \\[^xu] |               # x and u are case sensitive while hex letters are not
     \\x[0-9a-fA-F]{2} |     # Hexadecimal escape sequence
     \\u(?:
@@ -96,22 +93,21 @@
         10[0-9a-fA-F]{4}    # Unicode code point escapes from 100000 to 10FFFF
       )\}
     )
+///.toString().replace(/\//g,'')
+string_single_regex = ///
+  ^('''|')
+  (?:
+    [^\\] |
+    #{string_regex_craft}
   )*?
   \1
-///)
+///
+tokenizer.parser_list.push (new Token_parser 'string_literal', string_single_regex)
 double_quoted_regexp_craft = ///
   (?:
     [^\\#] |
     \#(?!\{) |
-    \\[^xu] |               # x and u are case sensitive while hex letters are not
-    \\x[0-9a-fA-F]{2} |     # Hexadecimal escape sequence
-    \\u(?:
-      [0-9a-fA-F]{4} |      # Unicode escape sequence
-      \{(?:
-        [0-9a-fA-F]{1,5} |  # Unicode code point escapes from 0 to FFFFF
-        10[0-9a-fA-F]{4}    # Unicode code point escapes from 100000 to 10FFFF
-      )\}
-    )
+    #{string_regex_craft}
   )*?
 ///.toString().replace(/\//g,'')
 tokenizer.parser_list.push (new Token_parser 'string_non_interpolated_literal',          new RegExp '^("""|")'+double_quoted_regexp_craft+'\\1')


disable clearvalue

disable clearvalue

--- vue/tests/e2e/helpers/pageHelper.coffee
+++ vue/tests/e2e/helpers/pageHelper.coffee
@@ -81,12 +81,12 @@

   fillIn: (selector, value, wait) ->
     @waitFor(selector, wait)
-    test.clearValue(selector)
+    # test.clearValue(selector)
     test.setValue(selector, value)

   fillInAndEnter: (selector, value, wait) ->
     @waitFor(selector, wait)
-    test.clearValue(selector)
+    # test.clearValue(selector)
     test.setValue(selector, [value, test.Keys.ENTER])

   escape: ->


add to top of help see sage math cloud wiki

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -491,6 +491,8 @@
         <Row>
             <Col sm=10 smOffset=1 md=8 mdOffset=2 xs=12>

+                    See the SageMathCloud Wiki.
+

                     <Icon name='medkit'/><Space/><Space/>
                     In case of any questions or problems, do not hesitate to create a <ShowSupportLink />.


Add minimal error options

Add minimal error options

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -213,6 +213,15 @@
             <Icon style={@props.style} name='times' />

+SimpleX = ({onClick}) ->
+    e.preventDefault(); onClick()}>
+        <Icon name='times' />
+
+
+exports.SkinnyError = ({error_text, on_close}) ->
+

+         <SimpleX onClick={on_close} /> {error_text}
+

 error_text_style =
     marginRight : '1ex'


fix double search submit

fix double search submit

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -545,10 +545,6 @@
         switch e.keyCode
             when 17
                 @setState(ctrl_down : false)
-            when 13
-                # Some machine-browser combos don't register keyPress
-                # when ctrl is depressed and enter is pressed.
-                @submit(e)

     escape : ->
         @props.on_escape?(@state.value)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/floating_text_effect.coffee
+++ src/floating_text_effect.coffee
@@ -1,6 +1,11 @@
 ###*
 FloatingTextEffect is a simple subclass of `TextEffect`. It provides some defaults
 to move the text upward and fade it out over 0.5 seconds.
+
+    # adds a
+    engine.add 'FlTextEffect'
+      x: 50
+      y: 50

 @see TextEffect
 @name FloatingTextEffect


commented tab schema in Config

commented tab schema in Config

--- src/steroids/Config.coffee
+++ src/steroids/Config.coffee
@@ -21,6 +21,8 @@
     tabTitleShadowColor:        ""
     selectedTabTintColor:       ""
     selectedTabBackgroundImage: ""
+
+    # tabs: [{title: "default", icon:"icon.png", location: "http://localhost/index.html"}]
     tabs: []
   }
   worker: {}


remove more tag fixed methods

remove more tag fixed methods

--- src/stores/exercise.coffee
+++ src/stores/exercise.coffee
@@ -10,47 +10,6 @@

 EmptyFn = -> ''

-FixedTags = [{
-  base: 'blooms'
-  range: [1...8]
-  separator: '-'
-}, {
-  base: 'dok'
-  range: [1...4]
-  separator: ''
-}, {
-  base: 'time'
-  range: ['short', 'medium', 'long']
-  separator: '-'
-}]
-
-
-isFixedTagType = (tag, fixedTag) -> tag.indexOf("#{fixedTag.base}#{fixedTag.separator}") isnt -1
-
-getTagTypes = (tags) ->
-  #filter out all editable tags
-  editable = _.filter tags, (tag) ->
-    _.reduce(FixedTags, (memo, fixedTag) ->
-      memo and not isFixedTagType(tag, fixedTag)
-    , true)
-
-  fixedArray = _.filter tags, (tag) -> editable.indexOf(tag) is -1
-
-  #get all fixed tags with value attribute
-  fixed = _.map(FixedTags, (fixedTag) ->
-    value = _.reduce(fixedArray, (memo, tag) ->
-      if isFixedTagType(tag, fixedTag)
-        tag
-      else
-        memo
-    , '')
-
-    _.extend({}, fixedTag, {value})
-  )
-
-  #return both types of tags
-  return {fixed, editable}
-
 ExerciseConfig = {
   _loaded: cascadeLoad
   _asyncStatusPublish: {}
@@ -59,30 +18,13 @@

   updateStimulus: (id, stimulus_html) -> @_change(id, {stimulus_html})

-  # updateTags: (id, editableTags) ->
-  #   fixedTags = getTagTypes(@_get(id).tags).fixed
-  #   tags = editableTags.concat(_.pluck(fixedTags, 'value'))
-  #   tags = _.filter(tags, (tag) -> tag isnt '')
-  #   @_change(id, {tags})
-
-  # updateFixedTag:(id, oldTag, newTag) ->
-  #   tags = @_get(id).tags
-  #   if (not newTag)
-  #     tags = _.filter(tags, (tag) -> tag isnt oldTag)
-  #   else if (not oldTag)
-  #     tags.push(newTag)
-  #   else
-  #     tags = _.filter(tags, (tag) -> tag isnt oldTag)
-  #     tags.push(newTag)
-
-  #   @_change(id, {tags})
-
   addBlankPrefixedTag: (id, {prefix}) ->
     prefix += ':'
     tags = _.clone( @_get(id).tags )
     unless 0 is tags.indexOf(prefix) # is there already a blank one?
       tags.push(prefix)
       @_change(id, {tags})
+

   # Updates or creates a prefixed tag
@@ -155,10 +97,6 @@

     getStimulus: (id) -> @_local[id].stimulus_html

-    # getEditableTags: (id) -> getTagTypes(@_local[id].tags).editable
-
-    # getFixedTags: (id) -> getTagTypes(@_local[id].tags).fixed
-
     getPublishedDate: (id) -> @_local[id].published_at

     isPublished: (id) -> @_local[id].published_at


expire entry in 90 seconds

expire entry in 90 seconds

--- packages/rocketchat-tutum/startup.coffee
+++ packages/rocketchat-tutum/startup.coffee
@@ -14,9 +14,9 @@
 	client.rpush("frontend:#{process.env.TUTUM_CLIENT_HOST}", process.env.TUTUM_CLIENT_NAME)
 	client.rpush("frontend:#{process.env.TUTUM_CLIENT_HOST}", "http://#{process.env.TUTUM_IP_ADDRESS.split('/')[0]}:3000")

-	# removes the redis entry on a SIGTERM
+	# removes the redis entry in 90 seconds on a SIGTERM
 	process.on 'SIGTERM', ->
-		client.del("frontend:#{process.env.TUTUM_CLIENT_HOST}")
+		client.expire("frontend:#{process.env.TUTUM_CLIENT_HOST}", 90)

 	day = 86400000