Restored open-in settings.

Restored open-in settings.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -59,3 +59,5 @@
     fontSize: 14
     showInvisibles: true
   "autocomplete-plus": {}
+  "open-in":
+    applications: "SourceTree,Terminal,Finder,WebStorm"


re-calibration

re-calibration


Fix the pathology tests.

Fix the pathology tests.

--- test/e2e/subjectDetailSpec.coffee
+++ test/e2e/subjectDetailSpec.coffee
@@ -53,7 +53,9 @@
           profile[name] = getTable(name)

         # The encounter accessor properties.
-        for enc in ['Biopsy', 'BreastSurgery', 'Surgery', 'Assessment']
+        # TODO - use the encounter type attribute instead
+        # (cf. the encounter.jade TODO).
+        for enc in ['Biopsy', 'BreastSurgery', 'SarcomaSurgery']
           profile[enc.toLowerCase()] =
             profile.find("div[ng-switch-when='#{ enc }']")

@@ -320,7 +322,7 @@
       describe 'Encounters', ->
         describe 'Biopsy', ->
           biopsy = null
-
+
           before ->
             biopsy = clinicalProfile.then (profile) ->
               profile.find('div[ng-switch-when="Biopsy"]')
@@ -338,107 +340,132 @@

           # TODO - Test cases for genetic expression.

-          it 'should show the tumor length', ->
-            biopsy.then (table) ->
-              table.find(By.binding('extent.length')).then (length) ->
-                expect(length,
-                       'The Breast tumor length table is missing')
-                  .to.exist
-                expect(length.isDisplayed(),
-                       'The Breast tumor length field is not displayed')
+          it 'should show the biopsy panel', ->
+            expect(biopsy, 'The Breast Biopsy panel is missing')
+              .to.eventually.exist
+
+          describe 'Pathology', ->
+            pathology = null
+
+            before ->
+              # The first (and usually only) tumor panel.
+              pathology = biopsy.then (biopsy) ->
+                biopsy.findAll(By.repeater('tumor in pathology.tumors')).then (tumors) ->
+                  tumors[0]
+
+            it 'should show the pathology panel', ->
+              pathology.then (table) ->
+                expect(table, 'The pathology panel is missing').to.exist
+                expect(table.isDisplayed(), 'The pathology panel is not' +
+                                            ' displayed')
                   .to.eventually.be.true

-          it 'should show the tumor width', ->
-            biopsy.then (table) ->
-              table.find(By.binding('extent.width')).then (width) ->
-                expect(width,
-                       'The Breast tumor width table is missing')
-                  .to.exist
-                expect(width.isDisplayed(),
-                       'The Breast tumor width field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the tumor depth', ->
-            biopsy.then (table) ->
-              table.find(By.binding('extent.depth')).then (depth) ->
-                expect(depth,
-                       'The Breast tumor depth table is missing')
-                  .to.exist
-                expect(depth.isDisplayed(),
-                       'The Breast tumor depth field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM stage', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm')).then (stage) ->
-                expect(stage,
-                       'The Breast TNM stage table is missing')
-                  .to.exist
-                expect(stage.isDisplayed(),
-                       'The Breast TNM stage field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the Modified B-R tubular formation', ->
-            biopsy.then (table) ->
-              table.find(By.binding('grade.tubularFormation')).then (diff) ->
-                expect(diff,
-                       'The Modified B-R tubular formation table is missing')
-                  .to.exist
-                expect(diff.isDisplayed(),
-                       'The Modified B-R tubular formation field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the Modified B-R nuclear pleomorphism', ->
-            biopsy.then (table) ->
-              table.find(By.binding('grade.nuclearPleomorphism')).then (count) ->
-                expect(count,
-                       'The Modified B-R nuclear pleomorphism table is missing')
-                  .to.exist
-                expect(count.isDisplayed(),
-                       'The Modified B-R nuclear pleomorphism field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the Modified B-R mitotic count', ->
-            biopsy.then (table) ->
-              table.find(By.binding('grade.mitoticCount')).then (necrosis) ->
-                expect(necrosis,
-                       'The Modified B-R mitotic count is missing')
-                  .to.exist
-                expect(necrosis.isDisplayed(),
-                       'The Modified B-R mitotic count is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM size', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm.size')).then (size) ->
-                expect(size,
-                       'The Breast TNM size table is missing')
-                  .to.exist
-                expect(size.isDisplayed(),
-                       'The Breast TNM size field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM lymph status', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm.lymphStatus')).then (status) ->
-                expect(status,
-                       'The Breast TNM lymph status table is missing')
-                  .to.exist
-                expect(status.isDisplayed(),
-                       'The Breast TNM lymph status field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM metastasis', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm.metastasis')).then (metastasis) ->
-                expect(metastasis,
-                       'The Breast TNM metastasis table is missing')
-                  .to.exist
-                expect(metastasis.isDisplayed(),
-                       'The Breast TNM metastasis field is not displayed')
-                  .to.eventually.be.true
-
-        describe 'BreastSurgery', ->
+            it 'should show the tumor length', ->
+              pathology.then (table) ->
+                table.find(By.binding('extent.length')).then (length) ->
+                  expect(length,
+                         'The Breast tumor length table is missing')
+                    .to.exist
+                  expect(length.isDisplayed(),
+                         'The Breast tumor length field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the tumor width', ->
+              pathology.then (table) ->
+                table.find(By.binding('extent.width')).then (width) ->
+                  expect(width,
+                         'The Breast tumor width table is missing')
+                    .to.exist
+                  expect(width.isDisplayed(),
+                         'The Breast tumor width field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the tumor depth', ->
+              pathology.then (table) ->
+                table.find(By.binding('extent.depth')).then (depth) ->
+                  expect(depth,
+                         'The Breast tumor depth table is missing')
+                    .to.exist
+                  expect(depth.isDisplayed(),
+                         'The Breast tumor depth field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM stage', ->
+              pathology.then (table) ->
+                # Note: By.binding('tnm') resolves to multiple elements,
+                # perhaps because the elements bound to tnm attributes,
+                # e.g. tnm.size, are included. Therefore, select the
+                # multiple tnm elements and test the first, which will
+                # be the stage element.
+                table.findAll(By.binding('tnm')).then (stages) ->
+                  stage = stages[0]
+                  expect(stage, 'The Breast TNM stage table is missing')
+                    .to.exist
+                  expect(stage.isDisplayed(),
+                         'The Breast TNM stage field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the Modified B-R tubular formation', ->
+              pathology.then (table) ->
+                table.find(By.binding('grade.tubularFormation')).then (diff) ->
+                  expect(diff,
+                         'The Modified B-R tubular formation table is missing')
+                    .to.exist
+                  expect(diff.isDisplayed(),
+                         'The Modified B-R tubular formation field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the Modified B-R nuclear pleomorphism', ->
+              pathology.then (table) ->
+                table.find(By.binding('grade.nuclearPleomorphism')).then (count) ->
+                  expect(count,
+                         'The Modified B-R nuclear pleomorphism table is missing')
+                    .to.exist
+                  expect(count.isDisplayed(),
+                         'The Modified B-R nuclear pleomorphism field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the Modified B-R mitotic count', ->
+              pathology.then (table) ->
+                table.find(By.binding('grade.mitoticCount')).then (necrosis) ->
+                  expect(necrosis,
+                         'The Modified B-R mitotic count is missing')
+                    .to.exist
+                  expect(necrosis.isDisplayed(),
+                         'The Modified B-R mitotic count is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM size', ->
+              pathology.then (table) ->
+                table.find(By.binding('tnm.size')).then (size) ->
+                  expect(size,
+                         'The Breast TNM size table is missing')
+                    .to.exist
+                  expect(size.isDisplayed(),
+                         'The Breast TNM size field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM lymph status', ->
+              pathology.then (table) ->
+                table.find(By.binding('tnm.lymphStatus')).then (status) ->
+                  expect(status,
+                         'The Breast TNM lymph status table is missing')
+                    .to.exist
+                  expect(status.isDisplayed(),
+                         'The Breast TNM lymph status field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM metastasis', ->
+              pathology.then (table) ->
+                table.find(By.binding('tnm.metastasis')).then (metastasis) ->
+                  expect(metastasis,
+                         'The Breast TNM metastasis table is missing')
+                    .to.exist
+                  expect(metastasis.isDisplayed(),
+                         'The Breast TNM metastasis field is not displayed')
+                    .to.eventually.be.true
+
+        describe 'Surgery', ->
           surgery = null

           before ->
@@ -452,109 +479,40 @@
                                           ' displayed')
                 .to.eventually.be.true

-          it 'should show the residual cancer burden index', ->
-            surgery.then (table) ->
-              table.find(By.binding('rcb.rcbIndex')).then (score) ->
-                expect(score,
-                       'The RCB index table is missing')
-                  .to.exist
-                expect(score.isDisplayed(),
-                       'The RCB index field is not displayed')
+          describe 'Pathology', ->
+            pathology = null
+
+            before ->
+               pathology = surgery.then (surgery) ->
+                surgery.findAll(By.repeater('tumor in pathology.tumors')).then (tumors) ->
+                  tumors[0]
+
+            it 'should show the pathology panel', ->
+              pathology.then (table) ->
+                expect(table, 'The pathology panel is missing').to.exist
+                expect(table.isDisplayed(), 'The pathology panel is not' +
+                                            ' displayed')
                   .to.eventually.be.true

-          it 'should show the residual cancer burden class', ->
-            surgery.then (table) ->
-              table.find(By.binding('rcb.rcbClass')).then (cls) ->
-                expect(cls,
-                       'The RCB class table is missing')
-                  .to.exist
-                expect(cls.isDisplayed(),
-                       'The RCB class field is not displayed')
-                  .to.eventually.be.true
-
-        describe 'Assessment', ->
-          assessment = null
-
-          before ->
-            assessment = clinicalProfile.then (profile) ->
-              profile.find('div[ng-switch-when="Assessment"]')
-
-          it 'should show the assessment panel', ->
-            assessment.then (table) ->
-              expect(table, 'The Breast assessment panel is missing').to.exist
-              expect(table.isDisplayed(), 'The Breast assessment panel is not' +
-                                          ' displayed')
-                .to.eventually.be.true
-
-          it 'should show the TNM stage', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm')).then (stage) ->
-                expect(stage,
-                       'The Breast TNM stage table is missing')
-                  .to.exist
-                expect(stage.isDisplayed(),
-                       'The Breast TNM stage field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the Modified B-R tubular formation', ->
-            assessment.then (table) ->
-              table.find(By.binding('grade.tubularFormation')).then (diff) ->
-                expect(diff,
-                       'The Modified B-R tubular formation table is missing')
-                  .to.exist
-                expect(diff.isDisplayed(),
-                       'The Modified B-R tubular formation field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the Modified B-R nuclear pleomorphism', ->
-            assessment.then (table) ->
-              table.find(By.binding('grade.nuclearPleomorphism')).then (count) ->
-                expect(count,
-                       'The Modified B-R nuclear pleomorphism table is missing')
-                  .to.exist
-                expect(count.isDisplayed(),
-                       'The Modified B-R nuclear pleomorphism field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the Modified B-R mitotic count', ->
-            assessment.then (table) ->
-              table.find(By.binding('grade.mitoticCount')).then (necrosis) ->
-                expect(necrosis,
-                       'The Modified B-R mitotic count is missing')
-                  .to.exist
-                expect(necrosis.isDisplayed(),
-                       'The Modified B-R mitotic count is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM size', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm.size')).then (size) ->
-                expect(size,
-                       'The Breast TNM size table is missing')
-                  .to.exist
-                expect(size.isDisplayed(),
-                       'The Breast TNM size field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM lymph status', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm.lymphStatus')).then (status) ->
-                expect(status,
-                       'The Breast TNM lymph status table is missing')
-                  .to.exist
-                expect(status.isDisplayed(),
-                       'The Breast TNM lymph status field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM metastasis', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm.metastasis')).then (metastasis) ->
-                expect(metastasis,
-                       'The Breast TNM metastasis table is missing')
-                  .to.exist
-                expect(metastasis.isDisplayed(),
-                       'The Breast TNM metastasis field is not displayed')
-                  .to.eventually.be.true
+            it 'should show the residual cancer burden index', ->
+              pathology.then (table) ->
+                table.find(By.binding('rcb.rcbIndex')).then (score) ->
+                  expect(score,
+                         'The RCB index table is missing')
+                    .to.exist
+                  expect(score.isDisplayed(),
+                         'The RCB index field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the residual cancer burden class', ->
+              pathology.then (table) ->
+                table.find(By.binding('rcb.rcbClass')).then (cls) ->
+                  expect(cls,
+                         'The RCB class table is missing')
+                    .to.exist
+                  expect(cls.isDisplayed(),
+                         'The RCB class field is not displayed')
+                    .to.eventually.be.true

       describe 'Treatments', ->
         describe 'Neoadjuvant Treatment', ->
@@ -688,185 +646,119 @@
                                           ' displayed')
                 .to.eventually.be.true

-          it 'should show the tumor site', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tumor.location')).then (site) ->
-                expect(site, 'The Sarcoma site table is missing').to.exist
-                expect(site.isDisplayed(), 'The Sarcoma site field is not' +
-                                           ' displayed')
+          describe 'Pathology', ->
+            pathology = null
+
+            before ->
+              pathology = biopsy.then (biopsy) ->
+                biopsy.findAll(By.repeater('tumor in pathology.tumors')).then (tumors) ->
+                  tumors[0]
+
+            it 'should show the pathology panel', ->
+              pathology.then (table) ->
+                expect(table, 'The pathology panel is missing').to.exist
+                expect(table.isDisplayed(), 'The pathology panel is not' +
+                                            ' displayed')
                   .to.eventually.be.true

-          it 'should show the tumor histology', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tumor.histology')).then (histology) ->
-                expect(histology, 'The Sarcoma histology table is' +
-                                  ' missing').to.exist
-                expect(histology.isDisplayed(), 'The Sarcoma histology field' +
-                                                ' is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM stage', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm')).then (stage) ->
-                expect(stage,
-                       'The Sarcoma TNM stage table is missing')
-                  .to.exist
-                expect(stage.isDisplayed(),
-                       'The Sarcoma TNM stage field is not displayed')
-                  .to.eventually.be.true
-                expect(stage, 'The stage is undefined').not.to.equal("undefined")
-
-          it 'should show the FNCLCC differentiation', ->
-            biopsy.then (table) ->
-              table.find(By.binding('grade.differentiation')).then (diff) ->
-                expect(diff,
-                       'The FNCLCC differentiation table is missing')
-                  .to.exist
-                expect(diff.isDisplayed(),
-                       'The FNCLCC differentiation field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the FNCLCC mitotic count', ->
-            biopsy.then (table) ->
-              table.find(By.binding('grade.mitoticCount')).then (count) ->
-                expect(count,
-                       'The FNCLCC mitotic count table is missing')
-                  .to.exist
-                expect(count.isDisplayed(),
-                       'The FNCLCC mitotic count field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the FNCLCC necrosis score', ->
-            biopsy.then (table) ->
-              table.find(By.binding('grade.necrosis_score')).then (necrosis) ->
-                expect(necrosis,
-                       'The FNCLCC necrosis score is missing')
-                  .to.exist
-                expect(necrosis.isDisplayed(),
-                       'The FNCLCC necrosis score is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM size', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm.size')).then (size) ->
-                expect(size,
-                       'The Sarcoma TNM size table is missing')
-                  .to.exist
-                expect(size.isDisplayed(),
-                       'The Sarcoma TNM size field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM lymph status', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm.lymphStatus')).then (status) ->
-                expect(status,
-                       'The Sarcoma TNM lymph status table is missing')
-                  .to.exist
-                expect(status.isDisplayed(),
-                       'The Sarcoma TNM lymph status field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM metastasis', ->
-            biopsy.then (table) ->
-              table.find(By.binding('tnm.metastasis')).then (metastasis) ->
-                expect(metastasis,
-                       'The Sarcoma TNM metastasis table is missing')
-                  .to.exist
-                expect(metastasis.isDisplayed(),
-                       'The Sarcoma TNM metastasis field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the necrosis percent range', ->
-            biopsy.then (table) ->
-              table.find(By.binding('necrosis_percent')).then (range) ->
-                expect(range,
-                       'The Sarcoma necrosis percent range table is missing')
-                  .to.exist
-                expect(range.isDisplayed(),
-                       'The Sarcoma necrosis percent range field is not' +
-                       ' displayed')
-                  .to.eventually.be.true
-
-        describe 'Assessment', ->
-          assessment = null
-
-          before ->
-            assessment = clinicalProfile.then (profile) ->
-              profile.find('div[ng-switch-when="Assessment"]')
-
-          it 'should show the assessment panel', ->
-            assessment.then (table) ->
-              expect(table, 'The Sarcoma assessment panel is missing').to.exist
-              expect(table.isDisplayed(), 'The Sarcoma assessment panel is not' +
-                                          ' displayed')
-                .to.eventually.be.true
-
-          it 'should show the TNM stage', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm')).then (stage) ->
-                expect(stage,
-                       'The Sarcoma TNM stage table is missing')
-                  .to.exist
-                expect(stage.isDisplayed(),
-                       'The Sarcoma TNM stage field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the FNCLCC differentiation', ->
-            assessment.then (table) ->
-              table.find(By.binding('grade.differentiation')).then (diff) ->
-                expect(diff,
-                       'The FNCLCC differentiation table is missing')
-                  .to.exist
-                expect(diff.isDisplayed(),
-                       'The FNCLCC differentiation field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the FNCLCC mitotic count', ->
-            assessment.then (table) ->
-              table.find(By.binding('grade.mitoticCount')).then (count) ->
-                expect(count,
-                       'The FNCLCC mitotic count table is missing')
-                  .to.exist
-                expect(count.isDisplayed(),
-                       'The FNCLCC mitotic count field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the FNCLCC necrosis score', ->
-            assessment.then (table) ->
-              table.find(By.binding('grade.necrosis_score')).then (necrosis) ->
-                expect(necrosis,
-                       'The FNCLCC necrosis score is missing')
-                  .to.exist
-                expect(necrosis.isDisplayed(),
-                       'The FNCLCC necrosis score is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM size', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm.size')).then (size) ->
-                expect(size,
-                       'The Sarcoma TNM size table is missing')
-                  .to.exist
-                expect(size.isDisplayed(),
-                       'The Sarcoma TNM size field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM lymph status', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm.lymphStatus')).then (status) ->
-                expect(status,
-                       'The Sarcoma TNM lymph status table is missing')
-                  .to.exist
-                expect(status.isDisplayed(),
-                       'The Sarcoma TNM lymph status field is not displayed')
-                  .to.eventually.be.true
-
-          it 'should show the TNM metastasis', ->
-            assessment.then (table) ->
-              table.find(By.binding('tnm.metastasis')).then (metastasis) ->
-                expect(metastasis,
-                       'The Sarcoma TNM metastasis table is missing')
-                  .to.exist
-                expect(metastasis.isDisplayed(),
-                       'The Sarcoma TNM metastasis field is not displayed')
-                  .to.eventually.be.true
+            it 'should show the tumor site', ->
+              pathology.then (table) ->
+                table.find(By.binding('tumor.location')).then (site) ->
+                  expect(site, 'The Sarcoma site table is missing').to.exist
+                  expect(site.isDisplayed(), 'The Sarcoma site field is not' +
+                                             ' displayed')
+                    .to.eventually.be.true
+
+            it 'should show the tumor histology', ->
+              pathology.then (table) ->
+                table.find(By.binding('tumor.histology')).then (histology) ->
+                  expect(histology, 'The Sarcoma histology table is' +
+                                    ' missing').to.exist
+                  expect(histology.isDisplayed(), 'The Sarcoma histology field' +
+                                                  ' is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM stage', ->
+              pathology.then (table) ->
+                # See the Breast TNM test case above for the rationale of
+                # findAll as opposed to find.
+                table.findAll(By.binding('tnm')).then (stages) ->
+                  stage = stages[0]
+                  expect(stage,
+                         'The Sarcoma TNM stage table is missing')
+                    .to.exist
+                  expect(stage.isDisplayed(),
+                         'The Sarcoma TNM stage field is not displayed')
+                    .to.eventually.be.true
+                  expect(stage, 'The stage is undefined').not.to.equal("undefined")
+
+            it 'should show the FNCLCC differentiation', ->
+              pathology.then (table) ->
+                table.find(By.binding('grade.differentiation')).then (diff) ->
+                  expect(diff,
+                         'The FNCLCC differentiation table is missing')
+                    .to.exist
+                  expect(diff.isDisplayed(),
+                         'The FNCLCC differentiation field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the FNCLCC mitotic count', ->
+              pathology.then (table) ->
+                table.find(By.binding('grade.mitoticCount')).then (count) ->
+                  expect(count,
+                         'The FNCLCC mitotic count table is missing')
+                    .to.exist
+                  expect(count.isDisplayed(),
+                         'The FNCLCC mitotic count field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the FNCLCC necrosis score', ->
+              pathology.then (table) ->
+                table.find(By.binding('grade.necrosis_score')).then (necrosis) ->
+                  expect(necrosis,
+                         'The FNCLCC necrosis score is missing')
+                    .to.exist
+                  expect(necrosis.isDisplayed(),
+                         'The FNCLCC necrosis score is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM size', ->
+              pathology.then (table) ->
+                table.find(By.binding('tnm.size')).then (size) ->
+                  expect(size,
+                         'The Sarcoma TNM size table is missing')
+                    .to.exist
+                  expect(size.isDisplayed(),
+                         'The Sarcoma TNM size field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM lymph status', ->
+              pathology.then (table) ->
+                table.find(By.binding('tnm.lymphStatus')).then (status) ->
+                  expect(status,
+                         'The Sarcoma TNM lymph status table is missing')
+                    .to.exist
+                  expect(status.isDisplayed(),
+                         'The Sarcoma TNM lymph status field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the TNM metastasis', ->
+              pathology.then (table) ->
+                table.find(By.binding('tnm.metastasis')).then (metastasis) ->
+                  expect(metastasis,
+                         'The Sarcoma TNM metastasis table is missing')
+                    .to.exist
+                  expect(metastasis.isDisplayed(),
+                         'The Sarcoma TNM metastasis field is not displayed')
+                    .to.eventually.be.true
+
+            it 'should show the necrosis percent range', ->
+              pathology.then (table) ->
+                table.find(By.binding('necrosis_percent')).then (range) ->
+                  expect(range,
+                         'The Sarcoma necrosis percent range table is missing')
+                    .to.exist
+                  expect(range.isDisplayed(),
+                         'The Sarcoma necrosis percent range field is not' +
+                         ' displayed')
+                    .to.eventually.be.true


remove empty func, thanks hound!

remove empty func, thanks hound!

--- test/hubot-confluence_test.coffee
+++ test/hubot-confluence_test.coffee
@@ -49,7 +49,6 @@
     # Create new robot, without http, using mock adapter
     robot = new Robot null, "mock-adapter", false

-    robot.adapter.on "connected", ->
     nconf.set("HUBOT_CONFLUENCE_USER", test_user)
     nconf.set("HUBOT_CONFLUENCE_PASSWORD", test_pw)
     nconf.set("HUBOT_CONFLUENCE_HOST", test_host)


Use screen_name instead of name

Use screen_name instead of name

--- src/scripts/tweet-content.coffee
+++ src/scripts/tweet-content.coffee
@@ -6,4 +6,4 @@

 			tweet = JSON.parse(body)

-			msg.send "@#{tweet.user.name}: #{tweet.text}"
+			msg.send "@#{tweet.user.screen_name}: #{tweet.text}"


make ensuring database exists optional

make ensuring database exists optional

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -79,9 +79,11 @@
                                  # identical permission checks in a single user query.
             cache_size   : 100   # cache this many queries; use @_query(cache:true, ...) to cache result
             concurrent_warn : 500
+            ensure_exists : true # ensure database exists on startup (runs psql in a shell)
         @setMaxListeners(10000)  # because of a potentially large number of changefeeds
         @_state = 'init'
         @_debug = opts.debug
+        @_ensure_exists = opts.ensure_exists
         dbg = @_dbg("constructor")  # must be after setting @_debug above
         dbg(opts)
         i = opts.host.indexOf(':')
@@ -172,8 +174,12 @@
         async.series([
             (cb) =>
                 @_concurrent_queries = 0
-                dbg("first make sure db exists")
-                @_ensure_database_exists(cb)
+                if @_ensure_exists
+                    dbg("first make sure db exists")
+                    @_ensure_database_exists(cb)
+                else
+                    dbg("assuming database exists")
+                    cb()
             (cb) =>
                 dbg("create client and start connecting...")
                 client = new pg.Client


fix completely broken project upgrades listing (next to billing)

--- src/smc-webapp/account_page.cjsx
+++ src/smc-webapp/account_page.cjsx
@@ -113,6 +113,7 @@
         connect_to = {}
         for x in misc.split('active_page autosave editor_settings email_address evaluate_key everywhere first_name forgot_password_error forgot_password_success groups last_name other_settings passports profile project_map remember_me reset_key reset_password_error show_forgot_password show_sign_out sign_in_error sign_out_error sign_up_error signing_in signing_up strategies stripe_customer terminal token')
             connect_to[x] = 'account'
+        connect_to['project_map'] = 'projects'
         actions = flux.getActions('account')
         <Flux flux={flux} connect_to={connect_to}>
             <AccountPage actions={actions} />


Fix attachment fixtures

Fix attachment fixtures

--- test/src/fixtures/fixtures.coffee
+++ test/src/fixtures/fixtures.coffee
@@ -6,6 +6,7 @@
   new Trix.Document blocks

 blockComment = ""
+cursorTarget = """#{Trix.ZERO_WIDTH_SPACE}"""

 @fixtures =
   "bold text":
@@ -109,7 +110,7 @@
     figure.setAttribute("contenteditable", false)
     figure.appendChild(image)

-    html: "
#{blockComment}#{figure.outerHTML}
"
+    html: "
#{blockComment}#{cursorTarget}#{figure.outerHTML}#{cursorTarget}
"
     document: new Trix.Document [new Trix.Block text]

   "image attachment without dimensions": do ->
@@ -119,6 +120,7 @@
     text = Trix.Text.textForAttachmentWithAttributes(attachment)

     image = Trix.makeElement("img", src: attrs.url)
+    image.dataset.trixMutable = true

     figure = Trix.makeElement
       tagName: "figure"
@@ -132,7 +134,7 @@
     figure.setAttribute("contenteditable", false)
     figure.appendChild(image)

-    html: "
#{blockComment}#{figure.outerHTML}
"
+    html: "
#{blockComment}#{cursorTarget}#{figure.outerHTML}#{cursorTarget}
"
     document: new Trix.Document [new Trix.Block text]

   "file attachment": do ->
@@ -156,7 +158,7 @@
     caption = """
#{attrs.filename}#{attrs.filesize}
"""
     figure.innerHTML = caption

-    html: """
#{blockComment}#{link.outerHTML}
"""
+    html: """
#{blockComment}#{cursorTarget}#{link.outerHTML}#{cursorTarget}
"""
     document: new Trix.Document [new Trix.Block text]

   "nested quote and code formatted block":


Increase timeout.

Increase timeout.

--- src/spec/integration.spec.coffee
+++ src/spec/integration.spec.coffee
@@ -224,3 +224,4 @@
       console.log "E %j", err
       done err
     .done()
+  , 30000


fix: Restore loadKeyPair after stubbing in unit test

--- test/unit/AnvilConnectKeys.coffee
+++ test/unit/AnvilConnectKeys.coffee
@@ -307,6 +307,9 @@
         connectKeys = new AnvilConnectKeys '/test'
         keys = connectKeys.loadKeyPairs()

+      after ->
+        AnvilConnectKeys.loadKeyPair.restore()
+
       it 'should return the PEM public signing key', ->
         keys.sig.pub.should.equal 'PUBLIC SIGNING PEM'


Add backward compatible classes to TextEditorComponent's root node

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -63,6 +63,11 @@
     @domElementPool = new DOMElementPool
     @domNode = document.createElement('div')
     @domNode.classList.add('editor-contents')
+    # TODO: Remove these backwards compatible classes once everyone has
+    # transitioned to non-shadow DOM selectors.
+    @domNode.classList.add('editor-contents--private')
+    @domNode.classList.add('editor--private')
+
     @overlayManager = new OverlayManager(@presenter, @domNode, @views)
     @blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool)


fixing middle mouse button clipboard paste on linux (atom:atom#8648)

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -525,9 +525,17 @@
     @presenter.invalidateBlockDecorationDimensions(arguments...)

   onMouseDown: (event) =>
-    unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
-      # Only handle mouse down events for left mouse button on all platforms
-      # and middle mouse button on Linux since it pastes the selection clipboard
+    # Handle middle mouse button on linux platform only (paste clipboard)
+    if event.button is 1 and process.platform is 'linux'
+      if selection = require('./safe-clipboard').readText('selection')
+        screenPosition = @screenPositionForMouseEvent(event)
+        @editor.setCursorScreenPosition(screenPosition, autoscroll: false)
+        @editor.insertText(selection)
+        return
+
+    # Handle mouse down events for left mouse button only
+    # (except middle mouse button on linux platform, see above)
+    unless event.button is 0
       return

     return if event.target?.classList.contains('horizontal-scrollbar')
@@ -674,7 +682,6 @@
         stopDragging()
         @editor.finalizeSelections()
         @editor.mergeIntersectingSelections()
-      pasteSelectionClipboard(event)

     stopDragging = ->
       dragging = false
@@ -713,11 +720,6 @@

     scaleScrollDelta = (scrollDelta) ->
       Math.pow(scrollDelta / 2, 3) / 280
-
-    pasteSelectionClipboard = (event) =>
-      if event?.which is 2 and process.platform is 'linux'
-        if selection = require('./safe-clipboard').readText('selection')
-          @editor.insertText(selection)

     window.addEventListener('mousemove', onMouseMove)
     window.addEventListener('mouseup', onMouseUp)


Update waitFor

Update waitFor

--- project_template/spec/spec_helper.coffee
+++ project_template/spec/spec_helper.coffee
@@ -32,15 +32,16 @@
   timeout = null
   i = setInterval(
     ->
-      if (result = test())
-        clearInterval i
-        clearTimeout timeout
-        d.resolve(result)
+      return unless (result = test())
+      clearInterval i
+      clearTimeout timeout
+      d.resolve(result)
     2
   )
-  timeout = setTimeout(1500,
+  timeout = setTimeout(
     ->
       clearInterval i
       d.reject(new Error('waitFor never resolved'))
+    1500
   )
   d.promise()


style: Minor battery position change

style: Minor battery position change

--- UI/ubersicht/battery.coffee
+++ UI/ubersicht/battery.coffee
@@ -49,8 +49,8 @@
     scale = .55
     opacity = 1.0

-    top: 7px
-    left: 5px
+    top: 15px
+    right: 5px
     font-family: Helvetica Neue
     font-size: 1em * scale


minor formatting of test strings

minor formatting of test strings

--- tests.coffee
+++ tests.coffee
@@ -347,46 +347,86 @@
   'my custom error'

   # wrong error to raise exceptions, they must be thrown
-  '@someException <- Exception new initWith "my custom error". try ( 1 print. someException ) catch ( someException ) handle ( " caught the error I wanted" print )'
+  '@someException <- Exception new initWith "my custom error".\
+    try ( 1 print. someException )\
+    catch ( someException ) handle ( " caught the error I wanted" print )'
   '1'

   # wrong error to raise exceptions, they must be thrown
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. someException ) catch ( someException ) handle ( " caught the error I wanted" print )'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. someException )\
+    catch ( someException ) handle ( " caught the error I wanted" print )'
   '1'

   # wrong error to raise exceptions, they must be thrown
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. someException ) catch ( someOtherException ) handle ( " caught the error I wanted" print )'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. someException )\
+    catch ( someOtherException ) handle ( " caught the error I wanted" print )'
   '1'

   # thrown exception, note how the statement after the throw is not executed.
-  '@someException <- Exception new initWith "my custom error". try ( 1 print. throw someException. 2 print ) catch ( someException ) handle ( " caught the error I wanted" print )'
+  '@someException <- Exception new initWith "my custom error".\
+    try ( 1 print. throw someException. 2 print )\
+    catch ( someException ) handle ( " caught the error I wanted" print )'
   '1 caught the error I wanted'

   # thrown exception, note how the statement after the throw is not executed.
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. throw someException. 2 print ) catch ( someException ) handle ( " caught the error I wanted" print )'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. throw someException. 2 print )\
+    catch ( someException ) handle ( " caught the error I wanted" print )'
   '1 caught the error I wanted'

   # thrown exception, note how the statement after the throw is not executed.
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. throw someException. 2 print ) catch ( someOtherException ) handle ( " caught the error I wanted" print )'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. throw someException. 2 print )\
+    catch ( someOtherException ) handle ( " caught the error I wanted" print )'
   '1'

   # thrown exception, note how the statement after the throw is not executed.
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. throw someOtherException. 2 print ) catch ( someOtherException ) handle ( " caught the error the first time around" ) catch ( someException ) handle ( " caught the error the second time around" ) print'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. throw someOtherException. 2 print )\
+    catch ( someOtherException ) handle ( " caught the error the first time around" )\
+    catch ( someException ) handle ( " caught the error the second time around" ) print'
   '1 caught the error the first time around'

-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. throw someException. 2 print ) catch ( someOtherException ) handle ( " caught the error the first time around" ) catch ( someException ) handle ( " caught the error the second time around" ) print'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. throw someException. 2 print )\
+    catch ( someOtherException ) handle ( " caught the error the first time around" )\
+    catch ( someException ) handle ( " caught the error the second time around" ) print'
   '1 caught the error the second time around'

   # catch-all case 1
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. throw someOtherException. 2 print ) catch ( someOtherException ) handle ( " caught the error the first time around" ) catch ( someException ) handle ( " caught the error the second time around" ) catch all handle (" catch all branch") print'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. throw someOtherException. 2 print )\
+    catch ( someOtherException ) handle ( " caught the error the first time around" )\
+    catch ( someException ) handle ( " caught the error the second time around" )\
+    catch all handle (" catch all branch") print'
   '1 caught the error the first time around'

   # catch-all case 2
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". try ( 1 print. throw someException. 2 print ) catch ( someOtherException ) handle ( " caught the error the first time around" ) catch ( someException ) handle ( " caught the error the second time around" ) catch all handle (" catch all branch") print'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    try ( 1 print. throw someException. 2 print )\
+    catch ( someOtherException ) handle ( " caught the error the first time around" )\
+    catch ( someException ) handle ( " caught the error the second time around" )\
+    catch all handle (" catch all branch") print'
   '1 caught the error the second time around'

   # catch-all case 3
-  '@someException <- Exception new initWith "my custom error". @someOtherException <- Exception new initWith "my other custom error". @yetAnotherException <- Exception new initWith "another custom error that is only caught by the catch all branch". try ( 1 print. throw yetAnotherException. 2 print ) catch ( someOtherException ) handle ( " caught the error the first time around" ) catch ( someException ) handle ( " caught the error the second time around" ) catch all handle (" catch all branch") print'
+  '@someException <- Exception new initWith "my custom error".\
+    @someOtherException <- Exception new initWith "my other custom error".\
+    @yetAnotherException <- Exception new initWith "another custom error that is only caught by the catch all branch".\
+    try ( 1 print. throw yetAnotherException. 2 print )\
+    catch ( someOtherException ) handle ( " caught the error the first time around" )\
+    catch ( someException ) handle ( " caught the error the second time around" )\
+    catch all handle (" catch all branch") print'
   '1 catch all branch'

   #'@ a <- 5 someUndefinedMessage'


Remove outdated comment

Remove outdated comment

--- lib/assets/javascripts/unpoly/classes/change/open_layer.coffee
+++ lib/assets/javascripts/unpoly/classes/change/open_layer.coffee
@@ -69,7 +69,7 @@
     @layer.setupHandlers()

     # Change history before compilation, so new fragments see the new location.
-    @handleHistory() # location event soll hier nicht mehr automatuisch fliegen
+    @handleHistory()

     # Remember where the element came from to support up.reload(element).
     @setSource({ newElement: @content, @source })


Add help section to PB sidebar

Add help section to PB sidebar

--- app/pages/lab/project.cjsx
+++ app/pages/lab/project.cjsx
@@ -130,9 +130,23 @@

             }</PromiseRenderer>

+
+

+

+
Need some help?

+

+

+                <Link className="nav-list-item" to="lab-how-to">Read a tutorial</Link>
+

+

+                Ask for help on talk
+

+

+

+

-
+
Other actions

         <button type="button" className="minor-button" disabled={@state.deletionInProgress} onClick={@deleteProject}>Delete this project <LoadingIndicator off={not @state.deletionInProgress} /></button>{' '}
         {if @state.deletionError?

{@state.deletionError.message}
}


[grunt] watch for changes in data js and coffee files too

--- tasks/grunt-contrib-watch.coffee
+++ tasks/grunt-contrib-watch.coffee
@@ -13,7 +13,7 @@
       files: ['<%= path.source.static %>/{,**/}*']
       tasks: ['copy:static']
     data:
-      files: ['<%= path.source.data %>/{,**/}*.{json,yml}']
+      files: ['<%= path.source.data %>/{,**/}*.{json,yml,js,coffee}']
       tasks: ['nunjucks']
     locales:
       files: ['<%= path.source.locales %>/{,**/}*.po']


fixed the autoscroll problem

fixed the autoscroll problem

--- app/assets/javascripts/backbone/views/chatbox.js.coffee
+++ app/assets/javascripts/backbone/views/chatbox.js.coffee
@@ -37,7 +37,7 @@
       )

       $("#activity-c#{model.cid}").attr("id", "activity-#{model.get('id')}")
-      $scrollbox = $(event.target).parent().find(".paginated-activities")
+      $scrollbox = $('.channels-pane')
       $scrollbox.prop("scrollTop", $scrollbox.prop('scrollHeight'))
     })


Added code to propagate prepaid changes to all the redeemers. (#4807)

--- server/middleware/prepaids.coffee
+++ server/middleware/prepaids.coffee
@@ -62,6 +62,11 @@
       prepaid.set('maxRedeemers',parseInt(maxRedeemers))
     database.validateDoc(prepaid)
     prepaid = yield prepaid.save()
+    # Propagate the prepaid changes to all redeemers
+    for redeemer in prepaid.get('redeemers')
+      user = yield User.findById redeemer.userID
+      user.set('coursePrepaid', _.pick(prepaid.toObject(), '_id', 'startDate','endDate'))
+      user.save()
     res.status(200).send(prepaid.toObject())

   redeem: wrap (req, res) ->


valueless trees (structure & leaves only)

--- tree2.coffee
+++ tree2.coffee
@@ -157,10 +157,14 @@
     ctx.textAlign = 'center'
     ctx.textBaseline = 'middle'

-    if @label.length > 0
-      ctx.fillText(@label,0,2*@radius)
-
-    ctx.fillText(@value, 0, 0)
+    if @value? && ((@value.length? && @value.length > 0) || @value > 0)
+      ctx.fillText(@value, 0, 0)
+
+      if @label? && @label.length > 0
+        ctx.fillText(@label, 0, 2*@radius)
+    else if @label? && @label.length >0
+      ctx.fillText(@label, 0, 0)
+
     ctx.restore()

 class Inner extends Node
@@ -187,7 +191,8 @@
     ctx.textAlign = 'center'
     ctx.textBaseline = 'middle'

-    ctx.fillText(@value, 0, 0)
+    if @value?
+      ctx.fillText(@value, 0, 0)
     ctx.restore()

     if @child0?
@@ -328,18 +333,6 @@

 collection_dropdown_menu = [
   {
-    name: 'sort by weight',
-    action: (c, t) ->
-      console.log('sort by weight')
-      c.addAnimations(c.sortNodes( ((n1, n2) -> n2.value - n1.value), t))
-  },
-  {
-    name: 'delete'
-    action: (c, t) ->
-      console.log('delete')
-      c.delete_flag = true
-  },
-  {
     name: 'tidy',
     action: (c, t) ->
       console.log('tidy trees')
@@ -350,17 +343,44 @@
         console.log('not attempting to tidy multiple trees')
   },
   {
+    name: 'remove values'
+    action: (c, t) ->
+      console.log('remove values')
+      c.removeValues()
+  },
+  {
     name: 'Shannon-Fano',
     action: (c, t) ->
       console.log('Shannon-Fano')
-      c.reconstruct_as = ShannonFanoNodeCollection
+      if not c.nodes[0].value?
+        console.log('values required for Shannon-Fano')
+      else
+        c.reconstruct_as = ShannonFanoNodeCollection
   },
   {
     name: 'Huffman',
     action: (c, t) ->
       console.log('Huffman')
-      c.reconstruct_as = HuffmanNodeCollection
-  }
+      if not c.nodes[0].value?
+        console.log('values required for Huffman')
+      else
+        c.reconstruct_as = HuffmanNodeCollection
+  },
+  {
+    name: 'sort by weight',
+    action: (c, t) ->
+      console.log('sort by weight')
+      if not c.nodes[0].value?
+        console.log('weights/values required to sort by weight')
+      else
+        c.addAnimations(c.sortNodes( ((n1, n2) -> n2.value - n1.value), t))
+  },
+  {
+    name: 'delete'
+    action: (c, t) ->
+      console.log('delete')
+      c.delete_flag = true
+  },
 ]

 huffman_collection_dropdown_menu = [
@@ -726,6 +746,15 @@

     @addAnimations([new CollectionAnimation(anims, -1)])

+  removeValues: () ->
+    rvFn = (node) ->
+      if node.value?
+        node.saved_value = node.value
+      delete node.value
+
+    for n in @nodes
+      n.forAll(rvFn)
+
 class HuffmanNodeCollection extends NodeCollection
   constructor: (shape) ->
     super shape


Allow zero value in custom editable value

If html attribute data-datagrid-editable-value="0" then value was loaded from table cell, not data attribute. Value should be loaded from data-datagrid-editable-value attribute if exists.

--- assets/src/datagrid.coffee
+++ assets/src/datagrid.coffee
@@ -486,7 +486,7 @@

 		cellValue = cell.html().trim().replace('<br>', '\n')

-		if cell.data('datagrid-editable-value')
+		if cell.attr('data-datagrid-editable-value')
 			valueToEdit = cell.data('datagrid-editable-value')
 		else
 			valueToEdit = cellValue


Removed annoying old startup message

Removed annoying old startup message

--- server/s3manager/s3manager.coffee
+++ server/s3manager/s3manager.coffee
@@ -6,7 +6,6 @@
 s3 = new AWS.S3()
 Meteor.startup ->
   s3.listBuckets {}, (err, data)->
-    console.log "client bucket: "+bucket
     console.log "=== buckets ==="
     if err?
       console.log "Error loading buckets: "+err


Referral: add custom title and partial support

--- client/Account/views/referrermodal.coffee
+++ client/Account/views/referrermodal.coffee
@@ -3,7 +3,7 @@
     options.cssClass       = KD.utils.curry "referrer-modal", options.cssClass
     options.width          = 610
     options.overlay       ?= yes
-    options.title          = "Get free disk space!"
+    options.title        or= "Get free disk space!"
     options.url          or= KD.getReferralUrl KD.nick()
     options.onlyInviteTab ?= no
     options.tabs           =
@@ -14,7 +14,7 @@
         share              :
           customView       : KDCustomHTMLView
           cssClass         : "clearfix"
-          partial          : "
For each person registers with your referral code, \
+          partial          : options.partial or "
For each person registers with your referral code, \
             you'll get 250 MB free disk space for your VM, up to 16 GB total.

Share this code or invite your gmail contacts.
"
         invite             :
           customView       : KDCustomHTMLView


Don't replace a proxy with another proxy

--- client/lanes/models/AssociationMap.coffee
+++ client/lanes/models/AssociationMap.coffee
@@ -117,7 +117,7 @@

             association = model[name]

-            if association.isProxy
+            if association.isProxy and Lanes.u.isModel(value) and not value.isProxy
                 association.replaceWithModel(value, association_name: name)

             else if Lanes.u.isModel(association)


fixed wrong method name

fixed wrong method name

--- server/methods/removeUserFromRoom.coffee
+++ server/methods/removeUserFromRoom.coffee
@@ -17,7 +17,7 @@
 		if RocketChat.authz.hasRole(Meteor.user()._id, 'owner', room._id)
 			numOwners = RocketChat.authz.getUsersInRole('owner', room._id).fetch().length
 			if numOwners is 1
-				throw new Meteor.Error 'error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { method: 'leaveRoom' }
+				throw new Meteor.Error 'error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { method: 'removeUserFromRoom' }

 		removedUser = RocketChat.models.Users.findOneByUsername data.username


Include assigned US NPA back

Include assigned US NPA back

These NPA are not in service, according to the NPA report but are assigned to the US country, so to reduce breaking some user I've added them back.


when uploading do not parse the response for errors

--- src/scripts/configs/aloha.coffee
+++ src/scripts/configs/aloha.coffee
@@ -84,7 +84,11 @@

             setTimeout(window.GLOBAL_UPLOADER_HACK, 100)

-            return xhr.response
+            if xhr.status >= 200 and xhr.status < 300 or xhr.status is 304 or xhr.status is 302
+              return xhr.response
+
+            return null
+
           # Send files using the `file` field when POSTing
           uploadfield: 'file'
           uploadurl: "#{location.origin}/resources"


Fix autoscroll

Fix autoscroll

--- src/scripts/tutorialSteps.coffee
+++ src/scripts/tutorialSteps.coffee
@@ -47,12 +47,9 @@
       style: width: 600
       attach: x: 'left', y: 'bottom', to: '.field-guide a[href="#!/classify/bat-calls"]', at: x: 'left', y: 'top'
       onEnter: (tutorial) ->
-        html = $('html')
-        start = html.scrollTop()
+        start = $('html').scrollTop() || 0
         end = $('.field-guide').offset().top - ($(window).innerHeight() / 2)
-
-        $('

').css(opacity: 0).animate {opacity: 1}, step: (i) ->
-          html.get(0).scrollTop = start + ((end - start) * i)
+        $('html, body').animate scrollTop: end

     new Step
       delay: 100


more aggressively truncate dbg output from postgres client

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -363,7 +363,7 @@

     _dbg: (f) =>
         if @_debug
-            return (m) => winston.debug("PostgreSQL.#{f}: #{misc.trunc_middle(JSON.stringify(m), 1000)}")
+            return (m) => winston.debug("PostgreSQL.#{f}: #{misc.trunc_middle(JSON.stringify(m), 250)}")
         else
             return ->


add more debugging info to find out what is actually happening with this firewall test.

--- src/smc-project/local_hub.coffee
+++ src/smc-project/local_hub.coffee
@@ -180,6 +180,8 @@
             if err? and err.code == 'ETIMEDOUT'
                 winston.debug('test_firewall: timeout -> no action')
             else
+                winston.warn('test_firwall', res)
+                winston.warn('test_firwall', body)
                 winston.warn('test_firewall: request went through -> exiting with code 99')
                 process.exit(99)
         )
@@ -341,6 +343,7 @@
     .option('--test_firewall', 'Abort and exit w/ code 99 if internal GCE information is accessible')
     .parse(process.argv)

+
 start_server program.tcp_port, program.raw_port, (err) ->
     if err
         process.exit(1)


fix bad icon string

fix bad icon string

--- app/controllers/home.coffee
+++ app/controllers/home.coffee
@@ -3,7 +3,7 @@
 marko = require 'marko'
 request = require("request")

-yellowBallIcon = "i120"
+yellowBallIcon = "i607"
 greenBallIcon = "i606"
 redBallIcon = "i605"
 skullIcon = "i148"


removed unnecessary method

removed unnecessary method

stopLoading is idempotent.

--- app/js/views/buttons.coffee
+++ app/js/views/buttons.coffee
@@ -7,14 +7,13 @@

   startLoading: => @$el.addClass    "fl-loading"
   stopLoading:  => @$el.removeClass "fl-loading"
-  isLoading:    => @$el.hasClass    "fl-loading"

   setCoordinates: (top, left) =>
     return if @$el.hasClass 'active'
     Factlink.set_position_of_element top, left, window, @$el

   show: =>
-    @stopLoading() if @isLoading()
+    @stopLoading()
     Factlink.el.find('div.fl-button').removeClass('active')
     @$el.addClass 'active'


Use router to figure out workflow preview URL

--- app/pages/lab/workflow.cjsx
+++ app/pages/lab/workflow.cjsx
@@ -28,15 +28,11 @@
     forceReloader: 0

   workflowLink: ->
+    router = require '../../router'
     [owner, name] = @props.project.slug.split('/')
-    # React-router completely overrides clicking on links. Unbelievable.
-    viewParams = owner: owner, name: name
-    viewQuery = workflow: @props.workflow.id
-    currentLocation = location.origin + location.pathname + location.search
-    currentLocation += if location.search is '' then '?' else '&'
-    currentLocation += "reload=#{@state.forceReloader}"
-    viewHash = @makeHref 'project-classify', viewParams, viewQuery
-    currentLocation + viewHash
+    viewParams = {owner, name}
+    viewQuery = workflow: @props.workflow.id, reload: @state.forceReloader
+    router.makeHref 'project-classify', viewParams, viewQuery

   render: ->
     window.editingWorkflow = @props.workflow


fix new view test

fix new view test

--- test/integration/new_view.coffee
+++ test/integration/new_view.coffee
@@ -20,6 +20,9 @@
         done()

     context 'when I click on "More tools" in the toolbar', ->
+      before (done) ->
+        setTimeout done, 500 # wait for window to get big enough!
+
       before (done) ->
         wd40.click '#toolbar .new-view', ->
           browser.waitForElementByCss '#chooser .tool', 4000, done


Finish metadata test

Finish metadata test

--- test/integration/tool_rpc.coffee
+++ test/integration/tool_rpc.coffee
@@ -142,11 +142,11 @@
           text.should.not.be.empty
           done()

-      xit 'returns the correct tables', (done) ->
+      it 'returns the correct tables (i.e. none)', (done) ->
         wd40.getText '#sqlMetaDataText', (err, text) =>
           obj = JSON.parse text
-          should.exist obj?.table?.SurLeTable
-          should.exist obj?.table?.VoirLeLapin
+          # we've a non-existent SQLite database
+          obj.table.should.be.empty
           done()

     context 'when the tool sql push button is pressed', ->


spec: remove redundant test, fix indentation

we were running a test within a test, which is weird... probably a rebase
issue. this caused weird errors in safari. also, the two tests were
equivalent, so just removed one.

Change-Id: I78c46e77c124b0bc8de9bdd283e60dc7763401c1
Reviewed-on: https://gerrit.instructure.com/20141
Reviewed-by: Stanley Stuart <2b43fb8b7a234825d50dd49ce7892d78a59da8f3@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Product-Review: Jon Jensen <44f878afe53efc66b76772bd845eb65944ed8232@instructure.com>
QA-Review: Jon Jensen <44f878afe53efc66b76772bd845eb65944ed8232@instructure.com>

--- spec/coffeescripts/formToJSONSpec.coffee
+++ spec/coffeescripts/formToJSONSpec.coffee
@@ -24,41 +24,17 @@
         <input type="text" name="nested[arr][]"     value="2">
       """

-  test 'turns a form into an JSON object', ->
-
-    @form = $ '<form/>'
-    @form.html """
-      <input type="text" name="foo"               value="foo">
-      <input type="text" name="arr[]"             value="1">
-      <input type="text" name="arr[]"             value="2">
-      <input type="text" name="nested[foo]"       value="nested[foo]">
-      <input type="text" name="nested[bar]"       value="nested[bar]">
-      <input type="text" name="nested[baz][qux]"  value="nested[baz][qux]">
-      <input type="text" name="nested[arr][]"     value="1">
-      <input type="text" name="nested[arr][]"     value="2">
-    """
-
-
-    json = @form.toJSON()
-
-    equal     json.foo,             'foo'
-    deepEqual json.arr,             ['1','2']
-    equal     json.nested.foo,      'nested[foo]'
-    equal     json.nested.bar,      'nested[bar]'
-    equal     json.nested.baz.qux,  'nested[baz][qux]'
-    deepEqual json.nested.arr,      ['1', '2']
-
-    test "serializes to a JSON string correctly", ->
-      expected =
-        foo: 'foo'
+  test "serializes to a JSON string correctly", ->
+    expected =
+      foo: 'foo'
+      arr: ['1','2']
+      nested:
+        foo: 'nested[foo]'
+        bar: 'nested[bar]'
+        baz:
+          qux: 'nested[baz][qux]'
         arr: ['1','2']
-        nested:
-          foo: 'nested[foo]'
-          bar: 'nested[bar]'
-          baz:
-            qux: 'nested[baz][qux]'
-          arr: ['1','2']
-      equal JSON.stringify(expected), JSON.stringify(@form)
+    equal JSON.stringify(expected), JSON.stringify(@form)

   test """
     returns null if element with datetime_field enabled class has undefined


Tests: Move slow tests to the end

Tests: Move slow tests to the end

--- spec/runner.coffee
+++ spec/runner.coffee
@@ -73,17 +73,6 @@
         chai.expect(err.stack).to.contain 'return-thrown-error.js:3'
         done()

-  describe 'filter never returning data', ->
-    it 'should fail after timeout', (done) ->
-      @timeout 4000
-      filter = local 'never-returning'
-      page = ""
-      options = {}
-      solver.runJob filter, page, options, (err, solution, details) ->
-        chai.expect(err).to.be.a 'object'
-        chai.expect(err.message).to.contain 'TIMEOUT'
-        done()
-
   describe 'filter returning no solution and no error', ->
     it 'should fail and return error', (done) ->
       filter = local 'return-nothing'
@@ -154,21 +143,6 @@
         chai.expect(err).to.be.an 'object'
         chai.expect(err.message).to.contain 'function'
         chai.expect(err.message).to.contain 'line'
-        done()
-
-  describe 'filter with infinite loop', ->
-    it 'should timeout and return error', (done) ->
-      @timeout 9000
-      filter = local 'infinite-loop'
-      page = ""
-      options = {}
-      solver.runJob filter, page, options, (err, solution, details) ->
-        chai.expect(solution).to.not.exist
-        chai.expect(err).to.be.a 'object'
-        chai.expect(err.message).to.include 'hard timeout'
-        chai.expect(err.message).to.include 'logged before infinite loop'
-        chai.expect(err.message).to.include 'poly: starting'
-        chai.expect(err.stack, 'stack should not duplicate message').to.not.include 'poly: starting'
         done()

   describe 'passing options to filter', ->
@@ -273,3 +247,28 @@
         chai.expect(details.screenshots).to.eql {}
         done()

+  describe 'filter never returning data', ->
+    it 'should fail after timeout', (done) ->
+      @timeout 4000
+      filter = local 'never-returning'
+      page = ""
+      options = {}
+      solver.runJob filter, page, options, (err, solution, details) ->
+        chai.expect(err).to.be.a 'object'
+        chai.expect(err.message).to.contain 'TIMEOUT'
+        done()
+
+  describe 'filter with infinite loop', ->
+    it 'should timeout and return error', (done) ->
+      @timeout 9000
+      filter = local 'infinite-loop'
+      page = ""
+      options = {}
+      solver.runJob filter, page, options, (err, solution, details) ->
+        chai.expect(solution).to.not.exist
+        chai.expect(err).to.be.a 'object'
+        chai.expect(err.message).to.include 'hard timeout'
+        chai.expect(err.message).to.include 'logged before infinite loop'
+        chai.expect(err.message).to.include 'poly: starting'
+        chai.expect(err.stack, 'stack should not duplicate message').to.not.include 'poly: starting'
+        done()


fix withing konnector

fix withing konnector

--- server/konnectors/withings.coffee
+++ server/konnectors/withings.coffee
@@ -270,24 +270,24 @@
         log.info(
             "#{bloodPressuresToSave.length} blood pressure measures to save")

-        saveAll = (models, done) ->
+        saveAll = (modelClass, models, done) ->
             async.forEach models, (model, callback) ->
-                model.save callback
+                modelClass.create model, callback
             , (err) ->
                 done err

         log.info 'Save weights...'
-        saveAll measuresToSave, (err) ->
+        saveAll Weight, measuresToSave, (err) ->
             log.info 'Weights saved...'
             return callback err if err

             log.info 'Save heartbeats...'
-            saveAll heartBeatsToSave, (err) ->
+            saveAll HeartBeat, heartBeatsToSave, (err) ->
                 log.info 'Heartbeats saved...'
                 return callback err if err

                 log.info 'Save blood pressures...'
-                saveAll bloodPressuresToSave, (err) ->
+                saveAll BloodPressure, bloodPressuresToSave, (err) ->
                     log.info 'Blood pressures saved...'
                     return callback err if err

@@ -343,7 +343,7 @@

             log.info "Found #{stepsToSave.length} new steps measures to save!"
             saveInstance = (model, cb) ->
-                model.save cb
+                Steps.create model, cb

             async.forEach stepsToSave, saveInstance, (err) ->
                 return callback err if err?


Better input focus on editor and some fixed rendering bugs when switching modes

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -55,7 +55,13 @@
       read: 'full-view'
       present: 'present'
     }[mode] or ''
-    updateView() if old is 'present'
+    if mode in ['read', 'present']
+      editor.setOption 'readOnly', 'nocursor'
+      updateView()
+    else if mode in ['write', '']
+      editor.setOption 'readOnly', no
+      editor.focus()
+      updateView() if mode is ''
   slide: (nr) -> updateView()
   theme: (v) ->
     model.theme = v
@@ -83,27 +89,30 @@
       updateTitle()

 updateView = ->
-  if state.mode is 'present'
-    md = editor.getValue().split /\n\s*\n\s*----*\s*\n/
-    if state.slide < md.length
-      viewEl.innerHTML = marked md[state.slide || 0]
+  switch state.mode
+    when 'present'
+      md = editor.getValue().split /\n\s*\n\s*(?:[-*]\s*){3,}\n/
+      if state.slide < md.length
+        viewEl.innerHTML = marked md[state.slide || 0]
+      else
+        state.slide = md.length - 1
+    when 'read', 'write'
+      viewEl.innerHTML = marked editor.getValue()
     else
-      state.slide = md.length - 1
-  else
-    cline = editor.getCursor().line
-    md = editor.getValue().split '\n'
-    md[cline] += ''
-    md = md.join '\n'
-    viewEl.innerHTML = marked md
-    updateIndex() if state.index
-    updateToc() if state.toc
-    scrollTop = viewWrapEl.scrollTop
-    viewHeight = viewWrapEl.offsetHeight
-    cursorSpan = document.getElementById 'cursor'
-    cursorTop = cursorSpan.offsetTop
-    cursorHeight = cursorSpan.offsetHeight
-    if cursorTop < scrollTop or cursorTop > scrollTop + viewHeight - cursorHeight
-      viewWrapEl.scrollTop = cursorTop - viewHeight/2
+      cline = editor.getCursor().line
+      md = editor.getValue().split '\n'
+      md[cline] += ''
+      md = md.join '\n'
+      viewEl.innerHTML = marked md
+      updateIndex() if state.index
+      updateToc() if state.toc
+      scrollTop = viewWrapEl.scrollTop
+      viewHeight = viewWrapEl.offsetHeight
+      cursorSpan = document.getElementById 'cursor'
+      cursorTop = cursorSpan.offsetTop
+      cursorHeight = cursorSpan.offsetHeight
+      if cursorTop < scrollTop or cursorTop > scrollTop + viewHeight - cursorHeight
+        viewWrapEl.scrollTop = cursorTop - viewHeight/2

 updateTitle = ->
   document.title = (if saved then '' else '*')+docTitle()
@@ -121,6 +130,7 @@
   lineNumbers: no
   lineWrapping: yes
   dragDrop: no
+  autofocus: yes
 editor.on 'change', ->
   updateView()
   if initiated
@@ -162,9 +172,7 @@
   mode: ''
   toggleToc: -> state.toc = not state.toc
   toggleIndex: -> state.index = not state.index
-  gotoPresent: ->
-    state.mode = 'present'
-    updateView()
+  gotoPresent: -> state.mode = 'present'
   expandInput: ->
     state.mode = (if state.mode then '' else 'write')
   expandView: ->
@@ -178,7 +186,7 @@
     if e.ctrlKey
       if e.altKey
         hit = switch e.keyCode
-          when 26 then model.gotoPresent(); true # ctrl+alt+z
+          when 26 then state.mode = 'present'; true # ctrl+alt+z
           when 24 then state.mode = 'write'; true # ctrl+alt+x
           when 3 then state.mode = ''; true # ctrl+alt+c
           when 22 then state.mode = 'read'; true # ctrl+alt+v


datgui integration

datgui integration

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -5,7 +5,7 @@
   g.ve = new VisualEnvironment(document.getElementById("surface"))
   g.ve.ebox = new ElementBox(g.ve)
   g.ve.surface = new Surface(g.ve)
-  g.ve.datgui = new dat.GUI()
+  g.ve.datgui = null
   g.ve.render(g.ve)

 #Global event handlers
@@ -92,6 +92,16 @@
       @shp = new Shapes.Diamond(0x007474, x, y, z, 15)
       @shp.obj3d.userData = this
       @parent.obj3d.add(@shp.obj3d)
+      @props = {
+        name: "ctl",
+        software: [],
+        os: "Ubuntu1504-54-STD"
+      }
+
+    showProps: (f) ->
+      f.add(@props, 'name')
+      #f.add(@props, 'software')
+      f.add(@props, 'os')

     #cyjs generates the json for this object
     cyjs: ->
@@ -102,10 +112,18 @@
       @shp = new Shapes.Circle(0x0047ca, x, y, z, 15)
       @shp.obj3d.userData = this
       @parent.obj3d.add(@shp.obj3d)
-      #TODO you are here, all objects with changable props should have a props object
+      #TODO you are here, all objects with changable props should have a props
+      #object
       @props = {
-        capacity: 100
+        name: "rtr",
+        capacity: 100,
+        latency: 0
       }
+
+    showProps: (f) ->
+      f.add(@props, 'name')
+      f.add(@props, 'capacity')
+      f.add(@props, 'latency')

     #cyjs generates the json for this object
     cyjs: ->
@@ -116,6 +134,16 @@
       @shp = new Shapes.Rectangle(0x0047ca, x, y, z, 25, 25)
       @shp.obj3d.userData = this
       @parent.obj3d.add(@shp.obj3d)
+      @props = {
+        name: "sw"
+        capacity: 100,
+        latency: 0
+      }
+
+    showProps: (f) ->
+      f.add(@props, 'name')
+      f.add(@props, 'capacity')
+      f.add(@props, 'latency')

     #cyjs generates the json for this object
     cyjs: ->
@@ -124,6 +152,11 @@
     constructor: (@parent, from, to, x, y, z, isIcon = false) ->
       @ln = new Shapes.Line(0xababab, from, to, z)
       @ln.obj3d.userData = this
+      @props = {
+        name: "lnk",
+        capacity: 100,
+        latency: 0
+      }

       #TODO if ln itself is clicked on this messes up selection logic
       if isIcon
@@ -133,6 +166,11 @@
         @parent.obj3d.add(@shp.obj3d)
       else
         @parent.obj3d.add(@ln.obj3d)
+
+    showProps: (f) ->
+      f.add(@props, 'name')
+      f.add(@props, 'capacity')
+      f.add(@props, 'latency')

     #cyjs generates the json for this object
     cyjs: ->
@@ -208,7 +246,6 @@
     true

   moveSelection: -> #TODO
-

   glowMaterial: () ->
     cam = @.ve.camera
@@ -251,9 +288,37 @@
     obj.rotateZ(Math.PI/4)
     obj

+  clearPropsGUI: ->
+    if @ve.datgui?
+      @ve.datgui.destroy()
+      @ve.datgui = null
+
+  showPropsGUI: (s) ->
+    @ve.datgui = new dat.GUI()
+
+    addElem = (d, k, v) ->
+      if !d[k]?
+        d[k] = [v]
+      else
+        d[k].push(v)
+
+    dict = new Array()
+    addElem(dict, x.constructor.name, x) for x in s
+
+    addGuiElems = (typ, xs) =>
+      f = @ve.datgui.addFolder(typ)
+      x.showProps(f) for x in xs
+      f.open()
+
+    #@ve.datgui.addFolder(k) for k,v of dict
+
+    addGuiElems(k, v) for k,v of dict
+
+
   selectObj: (obj) ->

     @clearSelection()
+    @showPropsGUI([obj])

     if not obj.glowBubble?
       console.log "! select"
@@ -277,6 +342,7 @@
   clearSelection: ->
     delete gb.userData.glowBubble for gb in @selectGroup.children
     @selectGroup.children = []
+    @clearPropsGUI()
     @ve.render()


Replace any remaining `this` with `@`

Replace any remaining `this` with `@`

--- coffee/uTip.coffee
+++ coffee/uTip.coffee
@@ -221,7 +221,7 @@
     if $?
         plugin = "uTip"
         $.fn[plugin] = (options) ->
-            this.each ->
-                if not $(this).data("plugin_#{plugin}")
+            @.each ->
+                if not $(@).data("plugin_#{plugin}")
                     # Create uTip instance only if one is not already assigned
-                    $(this).data("plugin_#{plugin}", new uTip(this, options))+                    $(@).data("plugin_#{plugin}", new uTip(@, options))


add keyword tree node titles

add keyword tree node titles

--- src/textorum/tinymce/tree.coffee
+++ src/textorum/tinymce/tree.coffee
@@ -219,6 +219,9 @@
           .map((idx, ele) -> $(ele).text()).get().join(", ")
       when "title", "article-id" then $(node).text()
       when "journal-id", "issn", "publisher" then $(node).text()
+      when "kwd" then $(node).text()
+      when "kwd-group" then $(node).children('[data-xmlel="kwd"]')
+        .map((idx, ele) -> $(ele).text()).get().join(", ")
       when "body"
         nodecount = $(node).children('[data-xmlel="sec"]').length
         "#{nodecount} section" + (if nodecount == 1 then "" else "s")


Improving method watch and all regexes for classes again. Adding validator to assure the base classes (in case of a class that extends some another) are loaded properly, and warns the user about any dependency problems.

--- src/toaster/grains/script.coffee
+++ src/toaster/grains/script.coffee
@@ -18,31 +18,30 @@
 			# Titleize the type for use in the log messages bellow
 			type = StringUtil.titleize info.type

-			# seitch over created, deleted, updated and watching
+			# switch over created, deleted, updated and watching
 			switch info.action

 				# when a new file is created
 				when "created"
 					msg = "#{('New ' + info.type + ' created:').green}"
 					console.log "#{msg} #{info.path}"
-					@compile()

 				# when a file is deleted
 				when "deleted"
 					msg = "#{(type + ' deleted, stop watching: ').red}"
 					console.log "#{msg} #{info.path}"
-					@compile()

 				# when a file is updated
 				when "updated"
 					msg = "#{(type + ' changed').yellow}"
 					console.log "#{msg} #{info.path}"
-					@compile()

 				# when a file starts being watched
 				when "watching"
 					msg = "#{('Watching ' + info.type).cyan}"
 					console.log "#{msg} #{info.path}"
+
+			@compile() unless info.action is "watching"

 	compile: (cb) ->
 		# collect all files
@@ -203,16 +202,18 @@

 			# looping through all files
 			for file in files
-
-				# read file content and initialize dependencies array
+
+				# read file content and initialize dependencies
+				# and extends array
 				raw = fs.readFileSync file, "utf-8"
 				dependencies = []
-
+				baseclasses = []
+
 				# assemble some information about the file
-				filepath       = file.replace( @src, "" ).substr 1
-				filename       = /\w+\.\w+/.exec( filepath )[ 0 ]
-				filefolder     = filepath.replace( "/#{filename}", "") + "/"
-
+				filepath   = file.replace( @src, "" ).substr 1
+				filename   = /\w+\.\w+/.exec( filepath )[ 0 ]
+				filefolder = filepath.replace( "/#{filename}", "") + "/"
+				namespace  = ""
 				# if the file is in the top level
 				if filepath.indexOf("/") is -1
 					filefolder = ""
@@ -224,8 +225,8 @@
 				rgx = "(^|=\\s*)(class)+\\s+(\\w+)" +
 					  "(\\s*$|\\s+(extends)\\s+(\\w+)\\s*$)"

-				# validate if there is a class extends inside the file
-				rgx_ext = /(^|=\s*)(class)+\s+(\w+)\s+(extends)\s+(\w+)\s*$/m
+				# filter classes that extends another classes
+				rgx_ext = "(^|=\\s*)(class)\\s(\\w+)\\s(extends)\\s(\\w+)\\s*$"

 				# if there is a class inside the file
 				if raw.match( new RegExp rgx, "m" )?
@@ -237,19 +238,26 @@
 						# the parser thing, adding the package the headers
 						# declarations
 						repl = "pkg( '#{namespace}' ).$3 = $2 $3"
-						repl += "$4" if rgx_ext.test raw
+						repl += "$4" if new RegExp(rgx_ext, "m").test raw
 						raw = raw.replace new RegExp( rgx, "gm" ), repl

 					# assemble some more infos about the file.
 					#		classname: ClassName
 					#		namespace: package.subpackage
 					#		classpath: package.subpackage.ClassName
-					classname = raw.match( new RegExp( rgx, "m") )[2]
-
+					classname = raw.match( new RegExp( rgx, "m") )[3]
+
+					# assure namespace is correct
 					if namespace is ""
 						classpath = classname
 					else
 						classpath = "#{namespace}.#{classname}"
+
+					# colletcts the base classes, in case some class in the file
+					# extends something
+					for klass in raw.match( new RegExp rgx_ext, "gm" ) ? []
+						baseclass = klass.match( new RegExp(rgx_ext, "m"))[5]
+						baseclasses.push baseclass

 				# if the file was already buffered, prints a warning msg,
 				# skip it and move on
@@ -283,6 +291,7 @@
 					filefolder:    filefolder

 					dependencies:  dependencies
+					baseclasses:   baseclasses
 				}

 			# finally executes callback, passing the collected buffer
@@ -358,7 +367,7 @@
 			initd.push file

 			# if theres no dependencies, go to next file
-			continue if file.dependencies.length is 0
+			continue if !file.dependencies.length && !file.baseclasses.length

 			# otherwise loop thourgh all file dependencies
 			for dependency, index in file.dependencies
@@ -409,9 +418,22 @@

 					# ..and finally prints a warning msg
 					console.log "WARNING! ".bold.yellow,
-						"Dependency #{dependency.yellow.bold} not found",
-						"for class #{file.classpath.yellow.bold}."
-
+								"Dependency #{Dependencybold}".yellow,
+								"not found for class".yellow,
+								"#{file.classpath.yellow.bold}."
+
+			for bc in file.baseclasses
+				found = ArrayUtil.find( initd, bc, "classname" )
+				if found == null && !@missing[bc]
+					@missing[bc] = true
+					console.log "#{'WARNING!'.bold} Base class".yellow,
+								bc.bold.white,
+								"not found for class".yellow,
+								file.classname.bold.white,
+								"in file".yellow,
+								file.filepath.yellow
+
+
 		# after all, return everything in the proper order
 		files


oauth: redirect to /Account/Oauth instead of /Account/Externals

--- servers/lib/server/helpers.coffee
+++ servers/lib/server/helpers.coffee
@@ -153,7 +153,7 @@
   JSession.update {clientId}, $set:query, callback

 redirectOauth = (res, provider, err)->
-  res.redirect("/Account/Externals/?provider=#{provider}&error=#{err}")
+  res.redirect("/Account/Oauth?provider=#{provider}&error=#{err}")

 getAlias = do->
   caseSensitiveAliases = ['auth']


Added ability to set a selection limit.
Use:
Add a data-limit with an integer value to the <select> object, like so:
    <select id="mylist" multiple="multiple" data-limit="3">

Optionally, send in a limit_reached function to handle a selection beyond the limit that has been set.

This is in answer to a StackOverflow problem: http://stackoverflow.com/questions/16670950/how-to-limit-the-amount-of-options-selected-to-3-on-multiselect-in-wordpress#comment23987361_16670950

Example jsFiddle on use: http://jsfiddle.net/mori57/L5EMy/

--- source/coffee/image-picker.coffee
+++ source/coffee/image-picker.coffee
@@ -1,7 +1,18 @@
 #
 # Image Picker source
 # by Rodrigo Vera
+# added limit-functionality by Jason M. Batchelor
 #
+type = (obj) ->
+  if obj == undefined or obj == null
+    return String obj
+  classToType = new Object
+  for name in "Boolean Number String Function Array Date RegExp".split(" ")
+    classToType["[object " + name + "]"] = name.toLowerCase()
+  myClass = Object.prototype.toString.call obj
+  if myClass of classToType
+    return classToType[myClass]
+  return "object"

 jQuery.fn.extend({
   imagepicker: (opts = {}) ->
@@ -20,6 +31,8 @@
     changed:      undefined,
     clicked:      undefined,
     selected:     undefined,
+    limit:        0,
+    limit_reached:undefined,
   }
   jQuery.extend(default_options, opts)

@@ -30,6 +43,8 @@
   constructor: (select_element, @opts={}) ->
     @select         = jQuery(select_element)
     @multiple       = @select.attr("multiple") == "multiple"
+    @limit          = parseInt(@select.data("limit")) || @opts.limit
+    @limit_reached  = @select.data("limit-reached") || @opts.limit_reached
     @build_and_append_picker()

   build_and_append_picker: () ->
@@ -71,7 +86,11 @@
       if imagepicker_option.value() in @selected_values()
         imagepicker_option.option.prop("selected", false)
       else
-        imagepicker_option.option.prop("selected", true)
+        if @limit > 0 && (@selected_values().length < @limit)
+          imagepicker_option.option.prop("selected", true)
+        else
+          if type(@limit_reached) is "function"
+            @limit_reached.call();
     else
       if @has_implicit_blanks() && imagepicker_option.is_selected()
         @select.val("")


Guard against stack overflow.

Guard against stack overflow.

Note: not entirely sure what the actual limit is for modern browsers.

--- browser/util.coffee
+++ browser/util.coffee
@@ -1,10 +1,13 @@
 root = this

 # modern browsers slow the event loop when tab is not in focus,
-# so don't give up control!
+# so don't give up control! but guard against stack overflows, too.
+nonAsyncCount = 0
 root.asyncExecute = (fn) ->
-  if (document.hidden or document.mozHidden or
-      document.webkitHidden or document.msHidden)
+  if ((document.hidden or document.mozHidden or
+      document.webkitHidden or document.msHidden) and
+      nonAsyncCount++ < 10000)
     fn()
   else
+    nonAsyncCount = 0
     setTimeout(fn, 0)


Adding more tests for model.

Adding more tests for model.

--- spec/app/source/modelsSpec.coffee
+++ spec/app/source/modelsSpec.coffee
@@ -13,6 +13,11 @@

   beforeEach ->
     sinon.stub(jQuery, 'ajax')
+    # Creating one student
+    @student = new studentModel
+      name: 'John New'
+      age: 17
+      sex: 'M'

   afterEach ->
     jQuery.ajax.restore()
@@ -26,22 +31,15 @@
     expect(attrs).toEqual {name: 'John New', age: 17, sex: null}

   it 'Should fetch a model', () ->
-    # Creating one student
-    student = new studentModel
-      id: 4
-      name: 'John New'
-      age: 17
-    student.fetch
+    # Setting student id for fetching
+    @student.set 'id', 4
+    @student.fetch
       success: sinon.spy()
     expect(jQuery.ajax.calledWithMatch { url: '/students/4' }).toBe true

   it 'Should save a model', ->
-    # Creating one student
-    student = new studentModel
-      name: 'John New'
-      age: 20
-      sex: 'M'
-    student.save
+    # Saving student model
+    @student.save
       success: sinon.spy()
     expect jQuery.ajax.calledWithMatch
       url: '/students'
@@ -49,6 +47,39 @@
     expect jQuery.ajax.getCall(0).args[0].data
     .toEqual JSON.stringify
       name: 'John New'
-      age: 20
+      age: 17
       sex: 'M'

+  it 'Should react to changes', (done)->
+    @student.on 'change', (model) ->
+      expect(model.previousAttributes()).not.toEqual model.attributes
+      done()
+    @student.set 'name', 'Marty McFly'
+
+  it 'Should react to a specific attribute change', ->
+    name_callback = sinon.spy()
+    age_callback = sinon.spy()
+    @student.on 'change:name', name_callback
+    @student.on 'change:age', age_callback
+    @student.set 'name', 'David Gilmour'
+    expect(name_callback.called).toBe true
+    expect(age_callback.called).toBe false
+    # spy.args[0] references the arguments for the first call.
+    # So [0][0] references to the first call, first argumet: model
+    expect(name_callback.args[0][0].get 'name').toEqual 'David Gilmour'
+
+  it 'Should save an existing model', ->
+    @student.set 'id', 4
+    @student.set 'name', 'Ace Ventura'
+    @student.save
+      success: sinon.spy()
+    expect jQuery.ajax.calledWithMatch
+      url: '/students/4'
+    .toBe true
+    expect jQuery.ajax.getCall(0).args[0].data
+    .toEqual JSON.stringify
+      name: 'Ace Ventura'
+      age: 17
+      sex: 'M'
+      id: 4
+


Regression tests for #65

Regression tests for #65

--- spec/language-haskell-spec.coffee
+++ spec/language-haskell-spec.coffee
@@ -170,3 +170,45 @@
         { value : ' ', scopes : [ 'source.haskell', 'meta.type-signature.haskell' ] }
         { value : '()', scopes : [ 'source.haskell', 'meta.type-signature.haskell', 'constant.language.unit.haskell' ] }
       ]
+
+  describe 'regression test for 65', ->
+    it 'works with space', ->
+      data = 'data Foo = Foo {bar :: Bar}'
+      {tokens} = grammar.tokenizeLine(data)
+      expect(tokens).toEqual [
+        { value : 'data', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'storage.type.data.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : 'Foo', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.type-signature.haskell', 'entity.name.type.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : '=', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'keyword.operator.assignment.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : 'Foo', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'entity.name.tag.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : '{', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'keyword.operator.record.begin.haskell' ] }
+        { value : 'bar', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'entity.other.attribute-name.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell' ] }
+        { value : '::', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'keyword.other.double-colon.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'meta.type-signature.haskell' ] }
+        { value : 'Bar', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'meta.type-signature.haskell', 'entity.name.type.haskell' ] }
+        { value : '}', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'keyword.operator.record.end.haskell' ] }
+      ]
+
+    it 'works without space', ->
+      data = 'data Foo = Foo{bar :: Bar}'
+      {tokens} = grammar.tokenizeLine(data)
+      expect(tokens).toEqual [
+        { value : 'data', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'storage.type.data.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : 'Foo', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.type-signature.haskell', 'entity.name.type.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : '=', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'keyword.operator.assignment.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell' ] }
+        { value : 'Foo', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'entity.name.tag.haskell' ] }
+        { value : '{', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'keyword.operator.record.begin.haskell' ] }
+        { value : 'bar', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'entity.other.attribute-name.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell' ] }
+        { value : '::', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'keyword.other.double-colon.haskell' ] }
+        { value : ' ', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'meta.type-signature.haskell' ] }
+        { value : 'Bar', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'meta.record-field.type-declaration.haskell', 'meta.type-signature.haskell', 'entity.name.type.haskell' ] }
+        { value : '}', scopes : [ 'source.haskell', 'meta.declaration.type.data.haskell', 'meta.declaration.type.data.record.block.haskell', 'keyword.operator.record.end.haskell' ] }
+      ]


use methodOnRemove

use methodOnRemove

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -373,7 +373,7 @@
         if error
           console.log("Error deleting #{name}", error)
           CoffeeAlerts.error("Error deleting #{name}: #{error.reason}")
-        else if type isnt "inlineUpdate"
+        else
           CoffeeAlerts.success("Deleted #{name}")
         @fetchRecordCount()
     else


removed seconds from all Vatikin columns except sunrise

--- source/javascripts/vatikin.coffee
+++ source/javascripts/vatikin.coffee
@@ -1,5 +1,3 @@
-show_hours_minutes_seconds = (t) -> t.format("h:mm:ss")
-
 class Vatikin
   constructor: (gregorianDate, hebrewDate) ->
     @today = moment(gregorianDate)
@@ -14,9 +12,9 @@
       when hebrewDate.inElul() && !hebrewDate.isRoshChodesh() then Vatikin.ELUL
       else Vatikin.WEEKDAY
   updateDOM: ->
-    $(".#{@day} .amidah").html(if @sunrise? then show_hours_minutes_seconds(@sunrise) else "??")
+    $(".#{@day} .amidah").html(if @sunrise? then @sunrise.format("h:mm:ss") else "??")
     for step in ['yishtabach', 'hodu', 'korbanot', 'selihot']
-      $(".#{@day} .#{step}").html(if @sunrise? && @schedule[step] then show_hours_minutes_seconds(@sunrise.subtract(@schedule[step], 'minutes')) else "")
+      $(".#{@day} .#{step}").html(if @sunrise? && @schedule[step] then @sunrise.subtract(@schedule[step], 'minutes').format("h:mm") else "")
     $(".#{@day} .selihot").removeClass('hidden') if @schedule.selihot

 (->