Refactor atom.coffee's package methods

Refactor atom.coffee's package methods

--- src/app/atom.coffee
+++ src/app/atom.coffee
@@ -12,39 +12,43 @@
   exitWhenDone: window.location.params.exitWhenDone
   loadedThemes: []
   pendingBrowserProcessCallbacks: {}
+  loadedPackages: []
+
+  loadPackage: (name) ->
+    if pack = Package.build(name)
+      @loadedPackages.push(pack)
+      pack.load()
+      pack
+
+  loadTextMatePackages: ->
+    @loadPackage(name) for name in @getPackageNames() when TextMatePackage.testName(name)

   loadPackages: ->
-    {packages, asyncTextMatePackages} = _.groupBy @getPackages(), (pack) ->
+    textMatePackages = []
+    for name in @getPackageNames()
+      pack = Package.build(name)
+      @loadedPackages.push(pack)
       if pack instanceof TextMatePackage and pack.name isnt 'text.tmbundle'
-        'asyncTextMatePackages'
+        textMatePackages.push(pack) if pack
       else
-        'packages'
+        pack.load()

-    pack.load() for pack in packages
-    if asyncTextMatePackages.length
-      new LoadTextMatePackagesTask(asyncTextMatePackages).start()
+    new LoadTextMatePackagesTask(textMatePackages).start() if textMatePackages.length > 0

-  getPackages: ->
-    @packages ?= @getPackageNames().map((name) -> Package.build(name))
-                                   .filter((pack) -> pack?)
-    new Array(@packages...)
-
-  getTextMatePackages: ->
-    @getPackages().filter (pack) -> pack instanceof TextMatePackage
-
-  loadPackage: (name) ->
-    Package.build(name)?.load()
+  getLoadedPackages: ->
+    _.clone(@loadedPackages)

   getPackageNames: ->
     disabledPackages = config.get("core.disabledPackages") ? []
-    allPackageNames = []
+    packageNames = []
     for packageDirPath in config.packageDirPaths
-      packageNames = fs.list(packageDirPath)
-        .filter((packagePath) -> fs.isDirectory(packagePath))
-        .map((packagePath) -> fs.base(packagePath))
-      allPackageNames.push(packageNames...)
-    _.unique(allPackageNames)
-      .filter (name) -> not _.contains(disabledPackages, name)
+      for packagePath in fs.list(packageDirPath) when fs.isDirectory(packagePath)
+        packageName = fs.base(packagePath)
+        continue if packageName in disabledPackages
+        continue if packageName in packageNames
+        packageNames.push(packageName)
+
+    packageNames

   loadThemes: ->
     themeNames = config.get("core.themes") ? ['atom-dark-ui', 'atom-dark-syntax']


Fix compatibility with atom 1.9.x #979

Fix compatibility with atom 1.9.x #979

Due to changes between atom 1.8 and 1.9 is editor.viewRegistry not availbale,
it has to be changed to atom.views.

--- src/beautify.coffee
+++ src/beautify.coffee
@@ -28,10 +28,10 @@
 # return data;
 # }
 getScrollTop = (editor) ->
-  view = editor.viewRegistry.getView(editor)
+  view = atom.views.getView(editor)
   view.getScrollTop()
 setScrollTop = (editor, value) ->
-  view = editor.viewRegistry.getView(editor)
+  view = atom.views.getView(editor)
   view.setScrollTop value

 getCursors = (editor) ->


Make sure to set an empty array for dependencies in the text binding constructor.

--- src/bindings.coffee
+++ src/bindings.coffee
@@ -218,6 +218,7 @@
   # Initializes a text binding for the specified view and text node.
   constructor: (@view, @el, @type, @keypath, @options = {}) ->
     @formatters = @options.formatters || []
+    @dependencies = []
     @setModel()

   # A standard routine binder used for text node bindings.


Match the entire string segment (including whitespace within the string). [#197]

--- src/bindings.coffee
+++ src/bindings.coffee
@@ -31,7 +31,7 @@
   # formatted value.
   formattedValue: (value) =>
     for formatter, fi in @formatters
-      args = formatter.split /\s+/
+      args = formatter.match /[^\s']+|'[^']+'/g
       id = args.shift()
       formatter = @view.formatters[id]


remove render context function

remove render context function

--- src/builtins.coffee
+++ src/builtins.coffee
@@ -72,9 +72,6 @@
       CodeMirror.runMode s, {name: 'javascript', json: true}, $pre.get(0)
       @add_rendered $pre

-    fn 'render', 'Renders an object', (o) ->
-      @render o
-
     fn 'md', 'Renders Markdown', (string) ->
       $html = $ '<div class="user-html"/>'
       $html.html marked string


Revert regression in the macro dependency analysis to take into account not compiled used

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -2677,7 +2677,8 @@
     usedNames = ctx.usedNames()
     requiredNames = (findDeps ctx) usedNames
     # Or do this directly from name compile?
-    if not isSetEmpty notCompiled = subtractSets requiredNames, ctx.auxiliaries()
+    notCompiled = subtractSets (concatSets (arrayToSet usedNames), requiredNames), ctx.auxiliaries()
+    if not isSetEmpty notCompiled
       ctx.doDefer call, _fst setToArray notCompiled
       deferCurrentDefinition ctx, call
       return deferredExpression()


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -13,7 +13,7 @@
 player.update()

-
+

 player.I.health #=> 51

 player.update()


Add id to next_page button to support phantomjs

--- app/assets/javascripts/components/paginator.js.coffee
+++ app/assets/javascripts/components/paginator.js.coffee
@@ -37,6 +37,7 @@
       )
       a(
         {
+          id: 'next_page',
           className: 'paginator-link',
           onClick: @props.incrementPage
         }


in process of fixing key issue

in process of fixing key issue

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -160,10 +160,10 @@
     task = @props.workflow.tasks[@props.annotation.task]
     mark = @props.annotation #@state.selectedMark
     # console.log "SubjectViewer#handleInitDrag"
-    console.log "mark._toolIndex", mark._toolIndex
-    console.log "task.tool_config.tools", task.tool_config.tools
-    console.log "task.tool_config.tools[mark._toolIndex]", task.tool_config.tools[mark._toolIndex]
-    console.log "markingTools[task.tool_config.tools[mark._toolIndex].type]", markingTools[task.tool_config.tools[mark._toolIndex].type]
+    # console.log "mark._toolIndex", mark._toolIndex
+    # console.log "task.tool_config.tools", task.tool_config.tools
+    # console.log "task.tool_config.tools[mark._toolIndex]", task.tool_config.tools[mark._toolIndex]
+    # console.log "markingTools[task.tool_config.tools[mark._toolIndex].type]", markingTools[task.tool_config.tools[mark._toolIndex].type]
     MarkComponent = markingTools[task.tool_config.tools[mark._toolIndex].type]

     if MarkComponent.initMove?
@@ -419,41 +419,66 @@
               </g>
           }

-          { # TODO: ANNOTATIONS SHOULD NOT BE AN ARRAY
-            # DISPLAY CURRENT ANNOTATION (MARK)
-            # for annotation in @props.classification.annotations
-            if @props.workflow.name is 'transcribe'
-
-              annotation = @props.annotation
-
+           { # HIGHLIGHT SUBJECT FOR TRANSCRIPTION
+            # TODO: Makr sure x, y, w, h are scaled properly
+
+            if @props.workflow.name in ['transcribe', 'verify']
+              toolName = @props.subject.region.toolName
+              mark = @props.subject.region
+              ToolComponent = markingTools[toolName]
+              isPriorMark = true
+              <g>
+                { @highlightMark(mark, toolName) }
+                <ToolComponent
+                  key={@props.subject.id}
+                  mark={mark}
+                  xScale={scale.horizontal}
+                  yScale={scale.vertical}
+                  disabled={isPriorMark}
+                  selected={mark is @state.selectedMark}
+                  getEventOffset={@getEventOffset}
+                  # ref={@refs.sizeRect}
+                  onSelect={@selectMark.bind this, @props.subject, mark}
+                />
+              </g>
+          }
+
+          { # HANDLE NEW MARKS
+            if @props.classification.annotation
+              annotation = @props.classification.annotation
+              console.log "HANDLING NEW ANNOTATION", annotation
               annotation._key ?= Math.random()
+              console.log "annotation._key", annotation._key
+              console.log "@props.classification.annotation._key", @props.classification.annotation._key
+
               isPriorMark = annotation isnt @props.annotation
-
-              # taskDescription = @props.workflow.tasks[annotation.task]
-
-              if @props.subject.region.toolName is 'pickOneMarkOne' and @props.annotationIsComplete #or taskDescription.tool is 'transcribe'
+              taskDescription = @props.workflow.tasks[annotation.task]
+              console.log "taskDescription", taskDescription
+
+              if taskDescription.tool is 'pickOneMarkOne' #or taskDescription.tool is 'transcribe'
+                console.log "yes it is pickOneMarkOne"
+                # console.log "annotation.key", annotation.key
+                console.log "annotation._key", annotation.key
                 <g key={annotation._key} className="marks-for-annotation" data-disabled={isPriorMark or null}>
-                  {
-
-                    console.log 'NEW MARK: ', mark, (mark.x), (mark.y+0)
-
-                    mark._key ?= Math.random()
-                    toolDescription = taskDescription.tool_config.tools[mark.tool]
+                    console.log "Do we make it here"
+                    console.log 'NEW MARK: ', annotation, (annotation.x), (annotation.y+0)
+
+                    annotation._key ?= Math.random()
+                    toolDescription = taskDescription.tool_config.tools[annotation._toolIndex]
+                    console.log "toolDescription", toolDescription

                     #adds task and description to each annotation
-                    @props.annotation["tool_task_description"] = @props.workflow.tasks[annotation.task].tool_config.tools[mark.tool]
-                    console.log "markingTools[toolDescription.type]", markingTools[toolDescription.type]
                     ToolComponent = markingTools[toolDescription.type]
-
+                    console.log "TOOL COMPONET", ToolComponent

                     <ToolComponent
-                      key={annotation._key}
-                      mark={annotation}
+                      key={mark._key}
+                      mark={mark}
                       xScale={scale.horizontal}
                       yScale={scale.vertical}
                       disabled={false}
                       isPriorMark={isPriorMark}
-                      selected={true}
+                      selected={mark is @state.selectedMark}
                       getEventOffset={@getEventOffset}
                       # ref={@refs.sizeRect}
                       submitMark={@submitMark}
@@ -462,9 +487,11 @@
                       onSelect={@selectMark.bind this, annotation, mark}
                       onDestroy={@destroyMark.bind this, annotation}
                     />
-                  }
+
                 </g>
+
             }
+
           </svg>

     #  Render any tools passed directly in in same parent div so that we can efficiently position them with respect to marks"


upate textRow highlighter

upate textRow highlighter

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -285,7 +285,8 @@
             }

             { # ROW FOCUS TOOL -------------------------------------------
-              if @props.workflow.name is "transcribe" and @state.subject.tool_task_description.type is "textRowTool"
+              if @props.workflow.name is "transcribe" and @state.subject.location.spec.toolName is "textRowTool"
+                console.log 'ROW TOOL!'
                 markHeight = @state.subject.location.spec.yLower - @state.subject.location.spec.yUpper
                 <g>

@@ -311,7 +312,8 @@

             { # RECTANGLE FOCUS TOOL ------------------------------------------
-              if @props.workflow.name is "transcribe" and @state.subject.tool_task_description.type is "rectangleTool"
+              if @props.workflow.name is "transcribe" and @state.subject.location.spec.toolName is "rectangleTool"
+                console.log 'RECTANGLE TOOL!'
                 markHeight = @state.subject.location.spec.yLower - @state.subject.location.spec.yUpper
                 <g>


delete

delete

--- app/assets/javascripts/kc_courses/ware_reading.coffee
+++ app/assets/javascripts/kc_courses/ware_reading.coffee
@@ -1,32 +0,0 @@
-class @CustomReadingProgress
-
-  before_load: ($el) ->
-  loaded: ($el, data) ->
-  error: ($el, data) ->
-  finally: ($el) ->
-
-class @Ware_reading
-  constructor: (@configs) ->
-    @selector = @configs['selector']
-
-    if @selector
-      @$el = jQuery @selector
-      if @$el.length > 0
-        jQuery.each @$el, (index) =>
-          course_id = jQuery(event.target).data('data-course_id')
-
-          jQuery.ajax
-          method: "GET"
-          url: "/api/courses/#{course_id}/progress"
-          type: "json"
-          success: (info)=>
-
-
-
-jQuery(document).on 'ready page:load', ->
-  configs =
-    progress_class: CustomReadingProgress
-    selector: '.course'
-
-  window.ware_reading = new Ware_reading(configs)
-  window.ware_reading.load()


relationList fixed #86

relationList fixed #86

--- app/assets/javascripts/plugins/relationList.js.coffee
+++ app/assets/javascripts/plugins/relationList.js.coffee
@@ -15,14 +15,15 @@
         self.autocompleteSource request, response
       select: (event, ui) ->
         self.autocompleteSelect event, ui
-    ).data("autocomplete")._renderItem = @autocompleteIcon
+    ).data("uiAutocomplete")._renderItem = @autocompleteIcon

     # Never submit the autocompletion form
     element.on "submit", "form.add", (event) ->
       event.preventDefault()

     # Removal of relations
-    element.on "click", "a[rel=delete]", ->
+    element.on "click", "a[rel=delete]", (event) ->
+      event.preventDefault()
       li = $(this).closest("li")
       return  unless confirm("really delete?")
       $.ajax


Added the coffeescript code

Added the coffeescript code

--- app/assets/javascripts/uploadcare/locale/az.js.coffee
+++ app/assets/javascripts/uploadcare/locale/az.js.coffee
@@ -1,115 +1,120 @@
-ns.az =
-  uploading: 'Yüklənilir... Lütfən, gözləyin.'
-  loadingInfo: 'İnfo yüklənilir...'
-  errors:
-    default: 'Xəta'
-    baddata: 'Yanlış dəyər'
-    size: 'Fayl çox böyükdür'
-    upload: 'Yüklənilə bilmədi'
-    user: 'Yükləmə ləğv edildi'
-    info: 'İnfo yüklənə bilmədi'
-    image: 'Yalnız təsvirlərə icazə verilir'
-    createGroup: 'Fayl qrupu yaradıla bilmir'
-    deleted: 'Fayl silindi'
-  draghere: 'Faylı bura atın'
-  file:
-    one: '%1 fayl'
-    other: '%1 fayl'
-  buttons:
-    cancel: 'Ləğv edin'
-    remove: 'Silin'
-    choose:
-      files:
-        one: 'Fayl seçin'
-        other: 'Fayllar seçin'
-      images:
-        one: 'Təsvir seçin'
-        other: 'Təsvirlər seçin'
-  dialog:
-    done: 'Hazırdır'
-    showFiles: 'Faylları göstərin'
-    tabs:
-      names:
-        'empty-pubkey': 'Xoş gəlmisiniz'
-        preview: 'Önbaxış'
-        file: 'Lokal Fayllar'
-        url: 'İxtiyari linklər'
-        camera: 'Kamera'
-        facebook: 'Facebook'
-        dropbox: 'Dropbox'
-        gdrive: 'Google Disk'
-        instagram: 'Instagram'
-        vk: 'VK'
-        evernote: 'Evernote'
-        box: 'Box'
-        skydrive: 'OneDrive'
-        flickr: 'Flickr'
-        huddle: 'Huddle'
-      file:
-        drag: 'Faylı bura atın'
-        nodrop: 'Kompüterinizdən faylları yükləyin'
-        cloudsTip: 'Bulud yaddaşlar
və sosial xidmətlər'
-        or: 'or'
-        button: 'Lokal fayl seçin'
-        also: 'Həmçinin, buradan seçə bilərsiniz'
-      url:
-        title: 'Vebdən fayllar'
-        line1: 'Vebdən istənilən faylı götürün.'
-        line2: 'Sadəcə, link verin.'
-        input: 'Linkinizi bura yerləşdirin...'
-        button: 'Yükləyin'
-      camera:
-        capture: 'Şəkil çəkin'
-        mirror: 'Güzgü'
-        retry: 'Yenidən icazə sorğusu göndərin.'
-        pleaseAllow:
-          title: 'Lütfən, kameranıza giriş hüququ verin'
-          text: 'Bu saytdan kameranıza daxil olmaq icazəsi verildi. ' +
-                'Kameranız ilə şəkil çəkmək üçün bu sorğunu təsdiq etməlisiniz.'
-        notFound:
-          title: 'Kamera aşkar edilmədi'
-          text: 'Görünür, bu cihaza kamera qoşulmayıb.'
-      preview:
-        unknownName: 'naməlum'
-        change: 'Ləğv edin'
-        back: 'Geri'
-        done: 'Əlavə edin'
-        unknown:
-          title: 'Yüklənilir... Lütfən, önbaxış üçün gözləyin.'
-          done: 'Önbaxışı ötürün və qəbul edin'
-        regular:
-          title: 'Bu fayl əlavə edilsin?'
-          line1: 'Yuxarıdakı faylı əlavə etmək üzrəsiniz.'
-          line2: 'Lütfən, təsdiq edin.'
-        image:
-          title: 'Bu təsvir əlavə edilsin?'
+uploadcare.namespace 'uploadcare.locale.translations', (ns) ->
+  ns.az =
+    uploading: 'Yüklənilir... Lütfən, gözləyin.'
+    loadingInfo: 'İnfo yüklənilir...'
+    errors:
+      default: 'Xəta'
+      baddata: 'Yanlış dəyər'
+      size: 'Fayl çox böyükdür'
+      upload: 'Yüklənilə bilmədi'
+      user: 'Yükləmə ləğv edildi'
+      info: 'İnfo yüklənə bilmədi'
+      image: 'Yalnız təsvirlərə icazə verilir'
+      createGroup: 'Fayl qrupu yaradıla bilmir'
+      deleted: 'Fayl silindi'
+    draghere: 'Faylı bura atın'
+    file:
+      one: '%1 fayl'
+      other: '%1 fayl'
+    buttons:
+      cancel: 'Ləğv edin'
+      remove: 'Silin'
+      choose:
+        files:
+          one: 'Fayl seçin'
+          other: 'Fayllar seçin'
+        images:
+          one: 'Təsvir seçin'
+          other: 'Təsvirlər seçin'
+    dialog:
+      done: 'Hazırdır'
+      showFiles: 'Faylları göstərin'
+      tabs:
+        names:
+          'empty-pubkey': 'Xoş gəlmisiniz'
+          preview: 'Önbaxış'
+          file: 'Lokal Fayllar'
+          url: 'İxtiyari linklər'
+          camera: 'Kamera'
+          facebook: 'Facebook'
+          dropbox: 'Dropbox'
+          gdrive: 'Google Disk'
+          instagram: 'Instagram'
+          vk: 'VK'
+          evernote: 'Evernote'
+          box: 'Box'
+          skydrive: 'OneDrive'
+          flickr: 'Flickr'
+          huddle: 'Huddle'
+        file:
+          drag: 'Faylı bura atın'
+          nodrop: 'Kompüterinizdən faylları yükləyin'
+          cloudsTip: 'Bulud yaddaşlar
və sosial xidmətlər'
+          or: 'or'
+          button: 'Lokal fayl seçin'
+          also: 'Həmçinin, buradan seçə bilərsiniz'
+        url:
+          title: 'Vebdən fayllar'
+          line1: 'Vebdən istənilən faylı götürün.'
+          line2: 'Sadəcə, link verin.'
+          input: 'Linkinizi bura yerləşdirin...'
+          button: 'Yükləyin'
+        camera:
+          capture: 'Şəkil çəkin'
+          mirror: 'Güzgü'
+          retry: 'Yenidən icazə sorğusu göndərin.'
+          pleaseAllow:
+            title: 'Lütfən, kameranıza giriş hüququ verin'
+            text: 'Bu saytdan kameranıza daxil olmaq icazəsi verildi. ' +
+                  'Kameranız ilə şəkil çəkmək üçün bu sorğunu təsdiq etməlisiniz.'
+          notFound:
+            title: 'Kamera aşkar edilmədi'
+            text: 'Görünür, bu cihaza kamera qoşulmayıb.'
+        preview:
+          unknownName: 'naməlum'
           change: 'Ləğv edin'
-        crop:
-          title: 'Bu təsviri kəsin və əlavə edin'
-          done: 'Hazırdır'
-          free: 'pulsuz'
-        error:
-          default:
-            title: 'Ups!'
-            text: 'Yükləmə zamanı nəsə xəta baş verdi.'
-            back: 'Lütfən, y enidən cəhd edin.'
+          back: 'Geri'
+          done: 'Əlavə edin'
+          unknown:
+            title: 'Yüklənilir... Lütfən, önbaxış üçün gözləyin.'
+            done: 'Önbaxışı ötürün və qəbul edin'
+          regular:
+            title: 'Bu fayl əlavə edilsin?'
+            line1: 'Yuxarıdakı faylı əlavə etmək üzrəsiniz.'
+            line2: 'Lütfən, təsdiq edin.'
           image:
-            title: 'Yaınız təsvir faylları qəbul olunur.'
-            text: 'Lütfən, başqa fayl ilə cəhd edin.'
-            back: 'Təsvir seçin'
-          size:
-            title: 'Seçdiyiniz fayl limiti keçir.'
-            text: 'Lütfən, başqa fayl ilə cəhd edin.'
-          loadImage:
-            title: 'Xəta'
-            text: 'Təsvir yüklənilə bilmir'
-        multiple:
-          title: '%files%' fayl seçdiniz.
-          question: 'Bütün bu faylları əlavə etmək istəyirsiniz?'
-          tooManyFiles: 'Həddindən çox fayl seçdiniz. %max% maksimumdur.'
-          tooFewFiles: '%files% fayl seçdiniz. Ən azı %min% tələb olunur.'
-          clear: 'Hamısını silin'
-          done: 'Hazırdır'
-    footer:
-      text: 'Faylları yükləyən, yadda saxlayan və icra edən'
-      link: 'Uploadcare.com'+            title: 'Bu təsvir əlavə edilsin?'
+            change: 'Ləğv edin'
+          crop:
+            title: 'Bu təsviri kəsin və əlavə edin'
+            done: 'Hazırdır'
+            free: 'pulsuz'
+          error:
+            default:
+              title: 'Ups!'
+              text: 'Yükləmə zamanı nəsə xəta baş verdi.'
+              back: 'Lütfən, y enidən cəhd edin.'
+            image:
+              title: 'Yaınız təsvir faylları qəbul olunur.'
+              text: 'Lütfən, başqa fayl ilə cəhd edin.'
+              back: 'Təsvir seçin'
+            size:
+              title: 'Seçdiyiniz fayl limiti keçir.'
+              text: 'Lütfən, başqa fayl ilə cəhd edin.'
+            loadImage:
+              title: 'Xəta'
+              text: 'Təsvir yüklənilə bilmir'
+          multiple:
+            title: '%files% fayl seçdiniz.'
+            question: 'Bütün bu faylları əlavə etmək istəyirsiniz?'
+            tooManyFiles: 'Həddindən çox fayl seçdiniz. %max% maksimumdur.'
+            tooFewFiles: '%files% fayl seçdiniz. Ən azı %min% tələb olunur.'
+            clear: 'Hamısını silin'
+            done: 'Hazırdır'
+      footer:
+        text: 'Faylları yükləyən, yadda saxlayan və icra edən'
+        link: 'Uploadcare.com'
+
+uploadcare.namespace 'uploadcare.locale.pluralize', (ns) ->
+  ns.az = (n) ->
+    'other'


Show correct time zone abbreviation based on the actual time

--- app/assets/javascripts/utils/course_date_utils.coffee
+++ app/assets/javascripts/utils/course_date_utils.coffee
@@ -7,7 +7,7 @@
     return /^20\d{2}\-\d{2}\-\d{2}/.test(date) && moment(date).isValid()

   formattedDateTime: (datetime, showTime = false) ->
-    timeZoneAbbr = ' ' + (new Date).toString().split('(')[1].slice(0, -1)
+    timeZoneAbbr = ' ' + moment(datetime).toDate().toString().split('(')[1].slice(0, -1)
     format = "YYYY-MM-DD#{if showTime then ' HH:mm'}"
     return moment(datetime).format(format) + "#{if showTime then timeZoneAbbr}";


fix issue with disabling layer clicking

fix issue with disabling layer clicking

--- app/assets/javascripts/workspace/mapbox_layers.coffee
+++ app/assets/javascripts/workspace/mapbox_layers.coffee
@@ -42,13 +42,12 @@

   remove: (name, destroy = true) =>
     if @_layerList[name]?
-      @_layerList[name].remove(destroy)
-
       if @_layerList[name].supports.click
         @_layerList[name].eachSublayer (layer, index) =>
           ci = @clickable.indexOf(layer)
           @clickable.splice(ci, 1) if ci >= 0

+      @_layerList[name].remove(destroy)
       delete @_layerList[name] if destroy

   loading: () =>


Use new workflow configuration prop for storing multi-img layout

Remove debug

--- app/components/multi-image-subject-layout-editor.cjsx
+++ app/components/multi-image-subject-layout-editor.cjsx
@@ -14,7 +14,6 @@
   defaultLayout: 'grid2'

   render: ->
-    console.log @props.workflow.config
     multiImageLayout = @props.workflow.multi_image_layout ? @defaultLayout
     <ChangeListener target={@props.workflow}>{ =>
       <div className="multi-image-subject-layout-editor">
@@ -30,4 +29,4 @@
   handleSelectLayout: (e) ->
     layout = e.target.value
     @props.workflow.update
-      'config.multi_image_layout': layout
+      'configuration.multi_image_layout': layout


Update force layout after change relation direction

Closes #205

--- app/frontend/javascripts/visualization-edit.js.coffee
+++ app/frontend/javascripts/visualization-edit.js.coffee
@@ -204,6 +204,7 @@

   onRelationsChangeDirection: (relation) ->
     @visualizationCanvas.updateRelations()
+    @visualizationCanvas.updateForce true

   onRelationsRemove: (relation) ->
     @visualizationCanvas.removeRelation relation.attributes


Fix missing function

Fix missing function

--- public/src/app.cjsx
+++ public/src/app.cjsx
@@ -25,6 +25,6 @@
     if jqXHR.status == 401
       window.location.href = '/auth/login'

-  if helpers.getUserId()
+  if helpers.getUserFromDom()
     Router.run routes, Router.HistoryLocation, (Handler) ->
       React.render <Handler />, $('#cq-target')[0]


Properly parse the models

Properly parse the models

--- coffee/cilantro/models/exporter.coffee
+++ coffee/cilantro/models/exporter.coffee
@@ -18,4 +18,12 @@
             c.subscribe c.SESSION_OPENED, => @fetch(reset: true)
             c.subscribe c.SESSION_CLOSED, => @reset()

+        parse: (attrs) ->
+            if attrs? and attrs._links?
+                for key in Object.keys(attrs._links)
+                    # Ignore the exporter endpoint itself
+                    if key != "self"
+                        this.push(new ExporterModel(attrs._links[key]))
+                return this.models
+
     { ExporterModel, ExporterCollection }


fix breadcrump (#1889)

fix breadcrump (#1889)

--- BackofficeBundle/Resources/public/coffee/backboneRouter.coffee
+++ BackofficeBundle/Resources/public/coffee/backboneRouter.coffee
@@ -42,7 +42,10 @@
     return true

   afterRouteChanges: (selector) ->
-    $('#left-panel *:has(>a' + selector + ')').addClass 'active'
+    navElementSelector = '#left-panel nav li:has(a' + selector + ')';
+    userAccountNavSelector = '#left-panel div *:has(a' + selector + ')';
+    $(navElementSelector + ',' + userAccountNavSelector).addClass 'active'
+
     $('#left-panel *.current').removeClass 'current'
     $('#left-panel *:has(>a' + selector + ')').addClass 'current'
     openMenu $("#left-panel nav").data('opts').speed, $("#left-panel nav").data('opts').openedSign


Exposing request verb (POST, PUT, etc...) as option.verb

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -63,6 +63,7 @@

   defaultOptions:
+    verb: "POST"
     url: null
     parallelUploads: 2
     maxFilesize: 256 # in MB
@@ -366,12 +367,12 @@

     fields = createElement fieldsString
     if @element.tagName isnt "FORM"
-      form = createElement("""<form action="#{@options.url}" enctype="multipart/form-data" method="post"></form>""")
+      form = createElement("""<form action="#{@options.url}" enctype="multipart/form-data" method="#{@options.verb}"></form>""")
       form.appendChild fields
     else
       # Make sure that the enctype and method attributes are set properly
       @element.setAttribute "enctype", "multipart/form-data"
-      @element.setAttribute "method", "post"
+      @element.setAttribute "method", @options.verb
     form ? fields

@@ -544,7 +545,7 @@
   uploadFile: (file) ->
     xhr = new XMLHttpRequest()

-    xhr.open "POST", @options.url, true
+    xhr.open @options.verb, @options.url, true

     handleError = =>
       @errorProcessing file, xhr.responseText || "Server responded with #{xhr.status} code."


Tests: Teams: Enabled tests.

Tests: Teams: Enabled tests.

--- client/test/lib/teams/teams.coffee
+++ client/test/lib/teams/teams.coffee
@@ -38,26 +38,26 @@
       browser.end()

-  # loginTeam: (browser) ->
+  loginTeam: (browser) ->

-  #   teamsHelpers.loginTeam(browser)
-  #   browser.end()
+    teamsHelpers.loginTeam(browser)
+    browser.end()

-  # openTeamSettings: (browser) ->
+  openTeamSettings: (browser) ->

-  #   teamsHelpers.loginTeam(browser)
-  #   teamsHelpers.clickTeamSettings(browser)
+    teamsHelpers.loginTeam(browser)
+    teamsHelpers.clickTeamSettings(browser)

-  #   browser.end()
+    browser.end()

-  # seeTeamNameOnSideBar: (browser) ->
+  seeTeamNameOnSideBar: (browser) ->

-  #   user = teamsHelpers.loginTeam(browser)
+    user = teamsHelpers.loginTeam(browser)

-  #   teamsHelpers.seeTeamNameOnsideBar(browser, user.teamSlug)
-  #   browser.end()
+    teamsHelpers.seeTeamNameOnsideBar(browser, user.teamSlug)
+    browser.end()

   # checkTeamSettings: (browser) ->


Player: fixed registry indentation and semicolon

--- coffee/src/player.coffee
+++ coffee/src/player.coffee
@@ -20,38 +20,38 @@
       model: {acornModel: @acornModel, shellModel: @shellModel}
       eventhub: @

-    @registerShellModule (shellModule) =>
-      # static shell registry; created on first call
-      @shellRegistry ?= {}
+  @registerShellModule: (shellModule) =>
+    # static shell registry; created on first call
+    @shellRegistry ?= {}

-      # validate that shellModule contains required String properties
-      required_properties = [ 'id', 'title', 'description' ]
-      _.each required_properties, (property) ->
-        unless shellModule[property]?
-          MissingParameterError "shell registration", property
+    # validate that shellModule contains required String properties
+    required_properties = [ 'id', 'title', 'description' ]
+    _.each required_properties, (property) ->
+      unless shellModule[property]?
+        MissingParameterError "shell registration", property

-        unless _.isString shellModule[property]
-          TypeError property, 'str'
+      unless _.isString shellModule[property]
+        TypeError property, 'str'

-      # validate that shellModule contains a Model object
-      unless shellModule.Model?
-        MissingParameterError "shell registration", "Model"
+    # validate that shellModule contains a Model object
+    unless shellModule.Model?
+      MissingParameterError "shell registration", "Model"

-      # populate shellModule with default properties from Shell
-      _.each acorn.shells.Shell, (value, key) ->
-        unless shellModule[key]?
-          shellModule[key] = _.clone value
+    # populate shellModule with default properties from Shell
+    _.each acorn.shells.Shell, (value, key) ->
+      unless shellModule[key]?
+        shellModule[key] = _.clone value

-      # validate shellModule's Model and View properties
-      class_properties = [ 'Model', 'ContentView', 'SummaryView', 'EditView' ]
-      _.each view_properties, (property) ->
-        unless _.isFunction shellModule[property]
-          TypeError property, 'class'
+    # validate shellModule's Model and View properties
+    class_properties = [ 'Model', 'ContentView', 'SummaryView', 'EditView' ]
+    _.each view_properties, (property) ->
+      unless _.isFunction shellModule[property]
+        TypeError property, 'class'

-      # set the `shell` property of each class within shellModule to point
-      # back to the shellModule namespace
-      _.each class_properties, (property) ->
-        shellModule[property].shell = shellModule
+    # set the `shell` property of each class within shellModule to point
+    # back to the shellModule namespace
+    _.each class_properties, (property) ->
+      shellModule[property].shell = shellModule

-      # register shell
-      @shellRegistry[shellModule.id] = shellModule
+    # register shell
+    @shellRegistry[shellModule.id] = shellModule


columnCount should be overriden even if reloading state

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -53,7 +53,6 @@
     @state = prevState.state if prevState.url == location.href
     @state ?= _.extend
       beatmaps: @props.beatmaps
-      columnCount: @columnCount()
       paging:
         page: 1
         url: laroute.route('beatmapsets.search')
@@ -63,6 +62,8 @@
       filters: null
       isExpanded: null
       @stateFromUrl()
+
+    @state.columnCount = @columnCount()

   columnCount: () ->


don't resolve aliases when dropping

don't resolve aliases when dropping

--- src/factoids.coffee
+++ src/factoids.coffee
@@ -64,7 +64,7 @@

   drop: (key) ->
     key = key.toLowerCase()
-    if factoids.get key
+    if factoids.get key, false
       delete factoids.data[key]
     else false


Fully functional font options

Fully functional font options

--- coffee/react-gui/components.coffee
+++ coffee/react-gui/components.coffee
@@ -213,7 +213,7 @@
     getText: -> @props.lc.tool?.text
     getInitialState: -> {
       text: @getText()
-      fontSize: 18
+      fontSizeString: 18
       isItalic: false
       isBold: false
       fontFamilyIndex: 0
@@ -229,36 +229,45 @@
         '"Lucida Typewriter",monospace')},
     ]

-    updateTool: ->
+    # LC's text tool API is a little funky: it just has a 'font' string you can
+    # set.
+    updateTool: (newState = {}) ->
+      for k of @state
+        unless k of newState
+          newState[k] = @state[k]
+      fontSize = parseInt(newState.fontSizeString or '18', 0)
       items = []
-      items.push('italic') if @state.isItalic
-      items.push('bold') if @state.isBold
-      items.push("#{@state.fontSize}px")
-      items.push(@getFamilies()[@state.fontFamilyIndex].value)
+      items.push('italic') if newState.isItalic
+      items.push('bold') if newState.isBold
+      items.push("#{fontSize}px")
+      items.push(@getFamilies()[newState.fontFamilyIndex].value)
       @props.lc.tool.font = items.join(' ')
-      console.log @props.lc.tool.font

     handleText: (event) ->
       @props.lc.tool.text = event.target.value
       @setState {text: @getText()}

     handleFontSize: (event) ->
-      @setState {fontSize: parseInt(event.target.value, 10)}
-      @updateTool()
+      newState = {fontSizeString: event.target.value}
+      @setState(newState)
+      @updateTool(newState)

     handleFontFamily: (event) ->
-      console.log event.target.value
-      @setState {fontFamilyIndex: event.target.value}
-      console.log @state.fontFamilyIndex, '!'
-      @updateTool()
+      newState = {fontFamilyIndex: event.target.value}
+      @setState(newState)
+      @updateTool(newState)

     handleItalic: (event) ->
-      @setState {isItalic: !@state.isItalic}
-      @updateTool()
+      newState = {isItalic: !@state.isItalic}
+      @setState(newState)
+      @updateTool(newState)

     handleBold: (event) ->
-      @setState {isItalic: !@state.isBold}
-      @updateTool()
+      newState = {isBold: !@state.isBold}
+      @setState(newState)
+      @updateTool(newState)
+
+    componentDidMount: -> @updateTool()

     render: ->
       {div, input, select, option, br, label, span} = React.DOM
@@ -267,7 +276,6 @@
         (input \
           {
             type: 'text'
-            key: 'text-value'
             placeholder: 'Enter text here'
             value: @state.text
             onChange: @handleText
@@ -281,37 +289,35 @@
         (input \
           {
             type: 'text'
-            key: 'font-size'
-            value: @state.fontSize
+            value: @state.fontSizeString
             onChange: @handleFontSize
           }
         )
         (select \
           {
-            key: 'font-family',
             value: @state.fontFamilyIndex,
             onChange: @handleFontFamily
           },
-          @getFamilies().map((family, ix) => (option {value: ix}, family.name))
-        )
-        (label {for: 'italic'},
+          @getFamilies().map((family, ix) =>
+            (option {value: ix, key: ix}, family.name)
+          )
+        )
+        (label {htmlFor: 'italic'},
           (input \
             {
               type: 'checkbox',
               id: 'italic',
-              key: 'italic',
               checked: @state.isItalic,
               onChange: @handleItalic
             },
             "italic"
           )
         )
-        (label {for: 'bold'},
+        (label {htmlFor: 'bold'},
           (input \
             {
               type: 'checkbox',
               id: 'bold',
-              key: 'bold',
               checked: @state.isBold,
               onChange: @handleBold,
             },


View.spec: test for render returning @

View.spec: test for render returning @

--- coffee/test/views/view.spec.coffee
+++ coffee/test/views/view.spec.coffee
@@ -43,6 +43,10 @@
     expect(view.rendering).toBe false
     view.render()
     expect(view.rendering).toBe true
+
+  it 'should have render return @', ->
+    view = new View()
+    expect(view.render()).toBe view

   it 'should support softRender', ->
     view1 = new View()


prevent that closure compiler renames the function

--- src/js/tools.coffee
+++ src/js/tools.coffee
@@ -1,5 +1,5 @@
 tools =
-	webp: ( useWebp ) ->
+	'webp': ( useWebp ) ->
 		(( $ ) ->
 			$('noscript[data-webp]')['each'] ( _, $t ) ->
 				$t = $ $t


Return null when not found

Return null when not found

--- src/wrappers/enumerable.coffee
+++ src/wrappers/enumerable.coffee
@@ -14,7 +14,7 @@
   find: (callback) ->
     for wrapper, index in @wrappers when callback(wrapper, index, @wrappers)
       return wrapper
-    false
+    null

   findIndex: (callback) ->
     for wrapper, index in @wrappers when callback(wrapper, index, @wrappers)


added additional style to attribution tag

--- bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
@@ -34,8 +34,9 @@
       attribution_div = Bokeh.$('.tile-attribution')
       if attribution_div.length == 0
         attribution_div = document.createElement('div')
-        bottom_offset = @map_plot.get('min_border_bottom')
-        right_offset = @map_plot.get('min_border_right')
+        border_width = 1
+        bottom_offset = @map_plot.get('min_border_bottom') + border_width
+        right_offset = @map_plot.get('min_border_right') + border_width
         Bokeh.$(attribution_div)
           .addClass('tile-attribution')
           .html(attribution)
@@ -43,6 +44,10 @@
           .css('bottom', bottom_offset.toString() + 'px')
           .css('right', right_offset.toString() + 'px')
           .css('background-color', 'rgba(255,255,255,.8)')
+          .css('font-size', '9pt')
+          .css('font-family', 'sans-serif')
+          .css('padding-left', '2px')
+          .css('padding-right', '2px')
           .appendTo(Bokeh.$(".bk-canvas-overlays"))
       else
         attribution_div = attribution_div[0]


Simplify

Simplify

--- client/explore.coffee
+++ client/explore.coffee
@@ -30,8 +30,7 @@
 getIsotopeContainer = () -> $("#isotope-container")

 createProjectElement = (project) ->
-  getThumbnail = () ->
-    if !R.isEmpty(project.pictures || []) then project.pictures[0].url else \
+  thumbnail = if !R.isEmpty(project.pictures || []) then project.pictures[0].url else \
       '/images/revox-reel-to-reel-resized.jpg'

   $("

@@ -40,7 +39,7 @@

#{project.title}

#{project.owner}

-
+

")[0]

@@ -56,8 +55,8 @@
         if ignore
           return
         logger.debug("A project (#{getQualifiedId(project)}) was added, updating Isotope grid")
-        $projElem = createProjectElement(project)
-        getIsotopeContainer().isotope("insert", $projElem)
+        projElem = createProjectElement(project)
+        getIsotopeContainer().isotope("insert", projElem)
       ,
       changed: (project) ->
         qualifiedId = getQualifiedId(project)


GroupHomePage: fix comma problem

GroupHomePage: fix comma problem

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -726,7 +726,7 @@
     @fetchMembershipPolicy (err, policy)=>
       if err then callback err
       else
-        homePageOptions = extend options {
+        homePageOptions = extend options, {
           @slug
           @title
           @avatar


groups: wip on finding out why permissions aren't saved

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -234,7 +234,14 @@
         if err
           callback err
         else if permissionSet?
-          permissionSet.update $set:{permissions}, callback
+          console.log 'updating permissions'
+          permissionSet.update
+            $set : {permissions}
+          , =>
+            for perm in permissionSet.permissions   #
+              if perm.role is 'guest'               #
+                console.log 'guest found in update' #
+            callback arguments...
         else
           permissionSet = new JPermissionSet {permissions}
           permissionSet.save callback
@@ -247,6 +254,9 @@
         if err
           callback err
         else
+          for perm in permissionSet.permissions     #
+            if perm.role is 'guest'                 #
+              console.log 'guest found in fetch'    #
           callback null, {
             permissionsByModule
             permissions: permissionSet.permissions


Fix topic details feed filtering

Fix topic details feed filtering

--- static/js/topic-details.coffee
+++ static/js/topic-details.coffee
@@ -90,9 +90,8 @@
     set_filter: (types) ->
         fname = 'activity__type__in'
         if not types
-            delete @collection.filters[fname]
-        else
-            @collection.filters[fname] = types.join(',')
+            types = @supported_types
+        @collection.filters[fname] = types.join(',')
         @collection.fetch reset: true

 SUPPORTED_TYPES = ['IN', 'GB', 'WQ']


workers/scriptblock: use cached CC info in the group - if exists

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -104,6 +104,14 @@

         currentGroup = group  if group

+        if currentGroup?.payment?.customer?.hasCard?
+          hasCard = yes
+          return fin()
+
+        hasCreditCard client, (err) ->
+          hasCard = not err?
+          return fin()
+
         fin()

     (fin) ->
@@ -153,11 +161,6 @@
         else console.error '[scriptblock] user not found', err
         fin()

-    (fin) ->
-      hasCreditCard client, (err) ->
-        hasCard = not err?
-        return fin()
-
   ]

   async.parallel queue, -> callback null, createHTML()


Added tracking for repost and share modal

--- core/app/backbone/views/facts/fact_bottom_view.coffee
+++ core/app/backbone/views/facts/fact_bottom_view.coffee
@@ -35,6 +35,8 @@
     FactlinkApp.Modal.show 'Repost Factlink',
       new AddToChannelModalView(collection: collection, model: @model)

+    mp_track "Factlink: Open repost modal"
+
   showStartConversation: (e) ->
     e.preventDefault()
     e.stopPropagation()
@@ -42,7 +44,9 @@
     FactlinkApp.Modal.show 'Send a message',
       new StartConversationView(model: @model)

+    mp_track "Factlink: Open share modal"
+
+
   openProxyLink: (e) ->
     mp_track "Factlink: Open proxy link",
       site_url: @model.get("fact_url")
-


changed conversions to work with new image magick feature pdf:fit-page=

--- services/filestore/app/coffee/FileConverter.coffee
+++ services/filestore/app/coffee/FileConverter.coffee
@@ -20,7 +20,9 @@
 		if !_.include approvedFormats, requestedFormat
 			err = new Error("invalid format requested")
 			return callback err
-		args = "nice convert -flatten -density 300 #{sourcePath} #{destPath}"
+		width = "600x"
+		args = "nice convert -define pdf:fit-page=#{width} -flatten -density 300 #{sourcePath} #{destPath}"
+		console.log args
 		exec args, childProcessOpts, (err, stdout, stderr)->
 			timer.done()
 			if err?
@@ -33,12 +35,8 @@
 		logger.log sourcePath:sourcePath, "thumbnail convert file"
 		destPath = "#{sourcePath}.png"
 		sourcePath = "#{sourcePath}[0]"
-		args =
-			src: sourcePath
-			dst: destPath
-			width: 424
-			height: 300
-		args = "nice convert -flatten -background white -resize 260x -density 300 #{sourcePath} #{destPath}"
+		width = "260x"
+		args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}"
 		exec args, childProcessOpts, (err, stdout, stderr)->
 			if err?
 				logger.err err:err, stderr:stderr, sourcePath:sourcePath, "something went wrong converting file to preview"
@@ -50,12 +48,8 @@
 		logger.log sourcePath:sourcePath, "preview convert file"
 		destPath = "#{sourcePath}.png"
 		sourcePath = "#{sourcePath}[0]"
-		args =
-			src: sourcePath
-			dst: destPath
-			width: 600
-			height: 849
-		args = "nice convert -flatten -background white -resize 548x -density 300 #{sourcePath} #{destPath}"
+		width = "548x"
+		args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}"
 		exec args, childProcessOpts, (err, stdout, stderr)->
 			if err?
 				logger.err err:err, stderr:stderr, sourcePath:sourcePath, destPath:destPath, "something went wrong converting file to preview"


preselect current date for DateQuestion

preselect current date for DateQuestion

--- app/assets/javascripts/components/questions/date_question.cjsx
+++ app/assets/javascripts/components/questions/date_question.cjsx
@@ -1,10 +1,13 @@
 React = require("react")
 moment = require("moment")
-DatePicker = require('react-datepicker')
+DatePicker = require("react-datepicker")
 QuestionnaireStore = require("stores/questionnaire_store")
 Question = require("components/question")

 class DateQuestion extends React.Component
+  componentDidMount: =>
+    QuestionnaireStore.selectAnswer(@props.data.id, moment().format("DD/MM/YYYY"))
+
   render: ->
     <DatePicker dateFormat="DD/MM/YYYY" maxDate={moment()}
       readOnly={true} selected={@selectedDate()} onChange={@handleChange}/>


change input value assignment behavior

change input value assignment behavior

--- app/assets/javascripts/uploadcare/widget/base-widget.js.coffee
+++ app/assets/javascripts/uploadcare/widget/base-widget.js.coffee
@@ -56,6 +56,7 @@
       if @settings.clearable
         @template.addButton('remove', t('buttons.remove')).on 'click', =>
           @__setObject(null)
+          @element.val('')

       @template.content.on 'click', '@uploadcare-widget-file-name', =>
         @openDialog()
@@ -77,7 +78,6 @@
       @currentObject = null
       object?.cancel?()
       @template.reset()
-      @element.val('')

     __setObject: (newFile) =>
       unless newFile == @currentObject


CO2 emissions chart: allow table format. Closes #1129

--- app/assets/javascripts/d3/co2_emissions.coffee
+++ app/assets/javascripts/d3/co2_emissions.coffee
@@ -8,14 +8,20 @@
       @end_year = App.settings.get('end_year')
       @initialize_defaults()

-    can_be_shown_as_table: -> false
+    can_be_shown_as_table: -> true

     outer_height: => @height + 10

-    draw: =>
+    # This method is called right before rendering because the series are
+    # added when the JSON request is complete, ie after the initialize method
+    #
+    setup_series: =>
       @serie_1990   = @series[0]
       @serie_value  = @series[1]
       @serie_target = @series[2]
+
+    draw: =>
+      @setup_series()

       margins =
         top: 20
@@ -141,3 +147,38 @@
         {id: 'co2_present', x: @start_year, y: @serie_value.safe_present_value()},
         {id: 'co2_future',  x: @end_year,   y: @serie_value.safe_future_value()}
       ]
+
+    # This chart has to override the standard render_as_table method
+    #
+    render_as_table: =>
+      # return false
+      @clear_container()
+      @setup_series()
+
+      unit = @model.get('unit')
+      raw_target = @serie_target.future_value()
+      target = if raw_target?
+        target = Metric.autoscale_value raw_target, unit
+      else
+        '-'
+      html = "
+

+

+

+              1990
+              #{@start_year}
+              #{@end_year}
+              Target
+

+

+

+

+              #{Metric.autoscale_value @serie_1990.safe_future_value(), unit}
+              #{Metric.autoscale_value @serie_value.safe_present_value(), unit}
+              #{Metric.autoscale_value @serie_value.safe_future_value(), unit}
+              #{target}
+

+

+

+      "
+      @container_node().html(html)


Fix up commit message

Fix up commit message

--- app/assets/javascripts/labels_select.js.coffee
+++ app/assets/javascripts/labels_select.js.coffee
@@ -41,6 +41,7 @@
             ''+
             ''+
             '<% }); %>');
+        labelNoneHTMLTemplate = _.template('
None
')

       if newLabelField.length and $dropdown.hasClass 'js-extra-options'
         $('.suggest-colors-dropdown a').on "click", (e) ->
@@ -176,6 +177,8 @@
           $selectbox.hide()
           $value.show()

+        multiSelect: $dropdown.hasClass 'js-multiselect'
+
         clicked: ->
           page = $('body').data 'page'
           isIssueIndex = page is 'projects:issues:index'
@@ -190,11 +193,9 @@
               .closest('.selectbox')
               .find('input[type="hidden"]')
               .val()
-            console.log 'selected', selected
             # need inline-block here instead of show,
             # which will default to the element's style in this case inline.
-            selected = if selected? then [ selected ] else ['']
-            console.log 'selected', selected
+            selected = if selected? then selected.split(',') else ['']
             $loading
               .fadeIn()
             $.ajax(
@@ -206,7 +207,11 @@
             ).done (data) ->
               $loading.fadeOut()
               $selectbox.hide()
+              if not data.labels.length
+                template = labelNoneHTMLTemplate()
+              else
+                template = labelHTMLTemplate(data)
               href = $value
                       .show()
-                      .html(labelHTMLTemplate(data))
+                      .html(template)
       )


button returns logs data to console, but maybe only one time.

--- assets/front.coffee
+++ assets/front.coffee
@@ -256,6 +256,10 @@
     s.on 'message', (data) ->
       if typeof data is "string"
         data = JSON.parse(data)
+        console.log(data.segment.tracks[0].metadata.artist)
+        console.log(data.segment.tracks[0].metadata.title)
+        console.log(data.segment.duration)
+        console.log(data)
       if data.listener_count?
         window._listeners = data.listener_count
       console.log "something happened."


fix unitialized attr

fix unitialized attr

--- bokehjs/src/coffee/renderer/glyph/glyph.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph.coffee
@@ -19,6 +19,8 @@
     for name, func of properties.factories
       @[name] = {}
       @[name] = _.extend(@[name], func(@model))
+
+    @warned = {}

     return @

@@ -139,8 +141,7 @@
     if @[func]?
       result = @[func](geometry)
     else if not @warned[geometry.type]?
-      logger.error("'#{geometry.type}' selection not available factories
-                    #{@model.type}")
+      logger.error("'#{geometry.type}' selection not available for #{@model.type}")
       @warned[geometry.type] = true

     return result


fix direction sense on FF

fix direction sense on FF

--- bokehjs/src/coffee/tool/wheel_zoom_tool.coffee
+++ bokehjs/src/coffee/tool/wheel_zoom_tool.coffee
@@ -43,10 +43,12 @@
     _zoom: (e) ->
       # voodoo to handle Firefox's inordinate stupidity
       if e.originalEvent?
+        # chrome and safari seem to take this branch
         if e.originalEvent.wheelDelta?
           delta = e.originalEvent.wheelDelta / -40
+        # various versions of FF apparently use these two
         else if e.originalEvent.deltaY?
-          delta = e.originalEvent.deltaY
+          delta = -e.originalEvent.deltaY
         else if e.originalEvent.detail?
           delta = e.originalEvent.detail
       screenX = e.bokehX


Build: remove some parens, and minor improvements

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -78,7 +78,7 @@
       .pipe rename "kd.#{version}js"
       .pipe gulp.dest "#{buildDir}/js"

-    stream.pipe(livereload())  if useLiveReload
+    stream.pipe livereload()  if useLiveReload

 gulp.task 'coffee-test', ->

@@ -91,9 +91,9 @@
     .pipe gulp.dest 'test'

   if useLiveReload
-    stream.pipe(livereload())
+    stream.pipe livereload()
     gulp.src './test/index.html'
-      .pipe(livereload())
+      .pipe livereload()

 gulp.task 'watch-test', ->

@@ -148,19 +148,24 @@
     .pipe gulp.dest "playground/js"

   if useLiveReload
-    stream.pipe(livereload())
+    stream.pipe livereload()
     gulp.src './playground/index.html'
       .pipe livereload()

-gulp.task 'live', ->

-  useLiveReload = yes
-
+gulp.task 'live', -> useLiveReload = yes

 gulp.task 'compile', ['styles', 'libs', 'coffee']

-gulp.task 'default', ['live', 'compile', 'coffee-test', 'play', 'watch-styles',  \
-                      'watch-coffee', 'watch-libs', 'watch-playground', 'watch-test'] , ->
+
+defaultTasks = [
+  'live', 'compile', 'coffee-test', 'play',
+  'watch-styles', 'watch-coffee', 'watch-libs',
+  'watch-playground', 'watch-test'
+]
+
+
+gulp.task 'default', defaultTasks , ->

   http.createServer ecstatic root : "#{__dirname}/playground"
     .listen(8080)


[fix] Clean import sequence

[fix] Clean import sequence

* Update import status properly: just once for each account.
* Typos in logging

--- server/models/konnector.coffee
+++ server/models/konnector.coffee
@@ -112,56 +112,57 @@

     @cleanFieldValues()

-    async.mapSeries @accounts, (values, next) =>
-        @runImport values, next
-    , (err, notifContents) =>
-        if err
-            errMessage = if err.message? then err.message else err.toString()
-            data =
-                isImporting: false
-                lastImport: new Date()
-                importErrorMessage: errMessage.replace(/<[^>]*>/ig, '')
-
-        else
-            data =
-                isImporting: false
-                lastSuccess: new Date()
-                lastImport: new Date()
-                importErrorMessage: null
-
-        @updateAttributes data, (err) ->
-            log.info 'Konnector metadate updated.'
-            callback err, notifContents
+    @updateAttributes isImporting: true, (err) =>
+        async.mapSeries @accounts, (values, next) =>
+            @runImport values, next
+        , (err, notifContents) =>
+            if err
+                log.error err
+                errMessage = if err.message? then err.message else err.toString()
+                data =
+                    isImporting: false
+                    lastImport: new Date()
+                    importErrorMessage: errMessage.replace(/<[^>]*>/ig, '')
+
+            else
+                data =
+                    isImporting: false
+                    lastSuccess: new Date()
+                    lastImport: new Date()
+                    importErrorMessage: null
+
+            @updateAttributes data, (err) ->
+                log.info 'Konnector metadata updated.'
+                callback err, notifContents

 Konnector::runImport = (values, callback) ->
-    @updateAttributes isImporting: true, (err) =>
-
-        if err?
-            log.error 'An error occured while modifying konnector state'
-            log.raw err
-
-            callback err
-
-        else
-            konnectorModule = konnectorHash[@slug]
-
-            @injectEncryptedFields()
-            values.lastSuccess = @lastSuccess
-            konnectorModule.fetch values, (importErr, notifContent) =>
-                fields = @getFields()
-                @removeEncryptedFields fields
-
-                if importErr? and \
-                typeof(importErr) is 'object' and \
-                importErr.message?
-                    callback importErr, notifContent
-
-                else if importErr? and typeof(importErr) is 'string'
-                    callback importErr, notifContent
-
-                else
-                    callback null, notifContent
+
+    if err?
+        log.error 'An error occured while modifying konnector state'
+        log.raw err
+
+        callback err
+
+    else
+        konnectorModule = konnectorHash[@slug]
+
+        @injectEncryptedFields()
+        values.lastSuccess = @lastSuccess
+        konnectorModule.fetch values, (importErr, notifContent) =>
+            fields = @getFields()
+            @removeEncryptedFields fields
+
+            if importErr? and \
+            typeof(importErr) is 'object' and \
+            importErr.message?
+                callback importErr, notifContent
+
+            else if importErr? and typeof(importErr) is 'string'
+                callback importErr, notifContent
+
+            else
+                callback null, notifContent

 # Append data from module file of curent konnector.
@@ -220,7 +221,7 @@
 # Patch function to move fieldValues field to accounts field. accounts field is
 # different because it's an array of field values (the goal is to allow several
 # accounts instead of one).
-Konnector::cleanFieldValues = (callback) ->
+Konnector::cleanFieldValues = ->

     if @fieldValues?
         @accounts ?= []


Using existing users list when updating fetch

--- server/modfetch/creator.coffee
+++ server/modfetch/creator.coffee
@@ -51,6 +51,7 @@
       throw new Meteor.Error 403, "Your ref must be 25 > characters > 3."
     fetch = _.pick fetch, _.keys defaultFetch
     fetch.status = 0
+    fetch.user = exist.user
     fetch.error = "You have not performed the initial fetch after the update."
     fetch._id = id
     clearExistingRepo id


Return Promise objects which are now used to receive the success and error callbacks. Also added deleteEvents and addEvents methods.

--- UoACalendarClient.coffee
+++ UoACalendarClient.coffee
@@ -110,7 +110,7 @@
   * @private
   ###

-  sendRequest: (path, method, data, onSuccess, onError) ->
+  sendRequest: (path, method, data, resolve, reject) ->
     getCookie = (name) ->
       nameEQ = name + "="
       ca = document.cookie.split(";")
@@ -153,39 +153,37 @@
         data += chunk
       )
       res.on('end', () ->
-        if (('' + res.statusCode).match(/^2\d\d$/))
-        # Request handled, happy
-          if onSuccess
-            parsed = {}
-            if data.length != 0
-              parsed = JSON.parse(data)
-
-            if parsed.constructor is Array
-              for k,v of parsed
-                if v.hasOwnProperty('start')
-                  v.start = new Date(v.start)
-
-                if v.hasOwnProperty('end')
-                  v.end = new Date(v.end)
-
-                if v.hasOwnProperty('lastUpdate')
-                  v.lastUpdate = new Date(v.lastUpdate)
-            else
-                if parsed.hasOwnProperty('start')
-                  parsed.start = new Date(parsed.start)
-
-                if parsed.hasOwnProperty('end')
-                  parsed.end = new Date(parsed.end)
-
-                if parsed.hasOwnProperty('lastUpdate')
-                  parsed.lastUpdate = new Date(parsed.lastUpdate)
-
-            onSuccess(res, parsed)
+        if (('' + res.statusCode).match(/^2\d\d$/)) # Request handled, happy
+          parsed = {}
+          if data.length != 0
+            parsed = JSON.parse(data)
+
+          if parsed.constructor is Array
+            for k,v of parsed
+              if v.hasOwnProperty('start')
+                v.start = new Date(v.start)
+
+              if v.hasOwnProperty('end')
+                v.end = new Date(v.end)
+
+              if v.hasOwnProperty('lastUpdate')
+                v.lastUpdate = new Date(v.lastUpdate)
+          else
+            if parsed.hasOwnProperty('start')
+              parsed.start = new Date(parsed.start)
+
+            if parsed.hasOwnProperty('end')
+              parsed.end = new Date(parsed.end)
+
+            if parsed.hasOwnProperty('lastUpdate')
+              parsed.lastUpdate = new Date(parsed.lastUpdate)
+
+          resolve({res: res, data: parsed})
         else
           # Server error, I have no idea what happend in the backend
           # but server at least returned correctly (in a HTTP protocol
           # sense) formatted response
-          if onError then onError(res, data) else console.error(res)
+          reject({res: res, data: data})
       )
     )

@@ -219,8 +217,9 @@
   * );
   ###

-  listCalendars: (onSuccess, onError) ->
-    @sendRequest('/calendars/', 'GET', 0, onSuccess, onError)
+  listCalendars: () ->
+      action = (resolve, reject) -> @sendRequest('/calendars/', 'GET', 0, resolve, reject)
+      new Promise(action.bind(@))

   ###*
   * Callback, returns an array of calendars.
@@ -266,8 +265,9 @@
   * );
   ###

-  getCalendar: (id, onSuccess, onError) ->
-    @sendRequest('/calendars/' + id + '/', 'GET', 0, onSuccess, onError)
+  getCalendar: (id) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/' + id + '/', 'GET', 0, resolve, reject)
+    new Promise(action.bind(@))

   ###*
   * Callback, returns particular calendar including its name and id. TODO: return custom fields here.
@@ -317,8 +317,9 @@
   * );
   ###

-  addCalendar: (name, onSuccess, onError) ->
-    @sendRequest('/calendars/', 'POST', {name: name}, onSuccess, onError)
+  addCalendar: (name) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/', 'POST', {name: name}, resolve, reject)
+    new Promise(action.bind(@))

   ###*
   * Callback function, executed when calendar successfully added.
@@ -365,8 +366,9 @@
   * );
   ###

-  deleteCalendar: (id, onSuccess, onError) ->
-    @sendRequest('/calendars/' + id + '/', 'DELETE', {}, onSuccess, onError)
+  deleteCalendar: (id) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/' + id + '/', 'DELETE', {}, resolve, reject)
+    new Promise(action.bind(@))

   ###*
   * Callback, executed when calendar successfully deleted.
@@ -413,8 +415,9 @@
   * );
   ###

-  listEvents: (calendarId, onSuccess, onError) ->
-    @sendRequest('/calendars/' + calendarId + '/events/', 'GET', 0, onSuccess, onError)
+  listEvents: (calendarId) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/' + calendarId + '/events/', 'GET', 0, resolve, reject)
+    new Promise(action.bind(@))

   ###*
   * Callback, executed when list events successful, data parameter contains list of events.
@@ -516,8 +519,28 @@
   * );
   ###

-  addEvent: (calendarId, event, onSuccess, onError) ->
-    @sendRequest('/calendars/' + calendarId + '/events/', 'POST', event, onSuccess, onError)
+  addEvent: (calendarId, event) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/' + calendarId + '/events/', 'POST', event, resolve, reject)
+    new Promise(action.bind(@))
+
+  addEvents: (calendarId, events) ->
+    action = (resolve, reject) ->
+      numEvents = events.length
+      count = 0
+      i = 0
+      while i < numEvents
+        @addEvent(calendarId, events[i])
+        .then (args) ->
+          count++
+          if count >= numEvents
+            resolve()
+        , (err) ->
+          count++
+          if count >= numEvents
+            reject()
+        i++
+      return
+    new Promise(action.bind(@))

   ###*
   * Callback, executed when event added successfully.
@@ -582,8 +605,29 @@
   * );
   ###

-  deleteEvent: (calendarId, eventId, onSuccess, onError) ->
-    @sendRequest('/calendars/' + calendarId + '/events/' + eventId + '/', 'DELETE', 0, onSuccess, onError)
+  deleteEvent: (calendarId, eventId) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/' + calendarId + '/events/' + eventId + '/', 'DELETE', 0, resolve, reject)
+    new Promise(action.bind(@))
+
+  deleteEvents: (calendarId, eventIds) ->
+    action = (resolve, reject) ->
+      numEvents = eventIds.length
+      count = 0
+      i = 0
+      while i < numEvents
+        eventId = eventIds[i]
+        @deleteEvent(calendarId, eventId)
+        .then (args) ->
+          count++
+          if count >= numEvents
+            resolve()
+        , (err) ->
+          count++
+          if count >= numEvents
+            reject()
+        i++
+      return
+    new Promise(action.bind(@))

   ###*
   * Callback, executed when event successfully deleted.
@@ -647,8 +691,9 @@
   * );
   ###

-  updateEvent: (calendarId, eventId, event, onSuccess, onError) ->
-    @sendRequest('/calendars/' + calendarId + '/events/' + eventId + '/', 'PATCH', event, onSuccess, onError)
+  updateEvent: (calendarId, eventId, event) ->
+    action = (resolve, reject) -> @sendRequest('/calendars/' + calendarId + '/events/' + eventId + '/', 'PATCH', event, resolve, reject)
+    new Promise(action.bind(@))

   ###*
   * Callback, when event updated.
@@ -701,14 +746,13 @@
   * );
   ###

-  findEvents: (calendarId, startDate, endDate, onSuccess, onError) ->
+  findEvents: (calendarId, startDate, endDate) ->
     toUTCString = (date) ->
       return (new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toISOString()

-    @sendRequest('/calendars/' + calendarId + '/find_events/', 'GET',
-      {startDate: toUTCString(startDate), endDate: toUTCString(endDate)},
-      onSuccess, onError
-    )
+    action = (resolve, reject) -> @sendRequest('/calendars/' + calendarId + '/find_events/', 'GET',
+      {startDate: toUTCString(startDate), endDate: toUTCString(endDate)}, resolve, reject)
+    new Promise(action.bind(@))

   ###*
   * Callback, returns events from a particular calendar within a date range.


start writing code to migrate from cassandra to rethinkdb: this isn't going to be easy!

--- salvus/cassandra.coffee
+++ salvus/cassandra.coffee
@@ -3110,6 +3110,59 @@
                         async.mapLimit(results, 10, g, (err) => cb(err))
         async.mapSeries(opts.token_ranges, f, opts.cb)

+    dump_table: (opts) =>
+        opts = defaults opts,
+            table       : required
+            columns     : required
+            consistency : @consistency
+            fetch_size  : 100
+            json        : []
+            each        : required    # each(row, cb) -- client calls the cb when done handline row.
+            cb          : required
+        query = "SELECT #{opts.columns.join(',')} FROM #{opts.table}"
+        winston.debug(query)
+        stream = @conn.stream(query, [], {fetchSize: opts.fetch_size, autoPage:true, consistency: opts.consistency})
+        process = (row) -> misc.pairs_to_obj([col,from_cassandra(row.get(col), col in opts.json)] for col in opts.columns)
+        stream.on 'readable', () ->
+            that = this
+            f = () ->
+                if not opts.cb?
+                    return
+                row = that.read()
+                if row
+                    row = process(row)
+                    opts.each row, (err) ->
+                        if err
+                            opts.cb?(err)
+                            delete opts.cb
+                        else
+                            f()
+            f()
+        stream.on 'end', () =>
+            opts.cb?()
+            delete opts.cb
+        stream.on 'error', (err) =>
+            opts.cb?(err)
+            delete opts.cb
+
+    rethink_migrate_central_log: (opts) =>
+        opts = defaults opts,
+            cb : required
+        db = require('rethink').rethinkdb()
+        table = db.table('central_log')
+        @dump_table
+            table   : 'central_log'
+            columns : ['time','event','value']
+            each    : (row, cb) ->
+                row.value = misc.from_json(row.value)
+                row.time = new Date(row.time)
+                table.insert(row, conflict:"replace").run(cb)
+            cb      : opts.cb
+
+    rethink_migrate_accounts: (opts) =>
+
+
+

 ############################################################################
 # Chunked storage for each project (or user, or whatever is indexed by a uuid).


remove a console log message that could result in a traceback if db failed.

--- salvus/cassandra.coffee
+++ salvus/cassandra.coffee
@@ -750,7 +750,6 @@
             consistency : 1     # since we really want optimal speed, and missing something temporarily is ok.
             limit     : 1000000  # TODO: probably start failing due to timeouts around 100K users (?) -- will have to cursor or query multiple times then?
             cb        : (err, results) =>
-                console.log("queried...", err, results.length)
                 if err and not @_all_users?
                     cb(err); return
                 v = []


forgot about step repeat in tests

forgot about step repeat in tests

--- test/gerber-parser_test.coffee
+++ test/gerber-parser_test.coffee
@@ -124,12 +124,13 @@

   describe 'step repeat', ->
     it 'should return a new step repeat block with good input', ->
+      p.format.places = [2, 2]
       p.parseCommand(param 'SRX1Y1').should.eql { new: { sr: { x: 1, y: 1 } } }
       p.parseCommand(param 'SRX1Y1I0J0').should.eql {
         new: { sr: { x: 1, y: 1, i: 0, j: 0 } }
       }
       p.parseCommand(param 'SRX2Y3I2.0J3.0').should.eql {
-        new: { sr: { x: 2, y: 3, i: 2, j: 3 } }
+        new: { sr: { x: 2, y: 3, i: 200, j: 300 } }
       }
       p.parseCommand(param 'SR').should.eql { new: { sr: { x: 1, y: 1 } } }
     it 'should throw if bad input', ->


added many more tests for 0.10 (more to come)

--- test/test-chain-builder.coffee
+++ test/test-chain-builder.coffee
@@ -38,7 +38,8 @@
   fn1 = ->
   fn2 = ->
   fn3 = ->
-  beforeEach -> chain = buildChain()
+  beforeEach ->
+    chain = buildChain()

   it 'should add single fn to array', ->
     chain.add fn1
@@ -87,40 +88,192 @@
     assert.strictEqual result.fn, arg

-# 3. chain.remove()
+# 3. remove
+# - chain.remove(index, 'reason')
+# - chain.remove('someid', 'reason')
+# - chain.remove(someFn, 'reason')
+# - control.remove('reason')
+# - chain.select(fn).remove('reason')
+
 describe 'test chain.remove()', ->
   fn1 = ->
+  fn1.options = id:'fn1'
   fn2 = ->
+  fn2.options = id:'fn2'
   fn3 = ->
+  fn3.options = id:'fn3'
   chain = null
-  beforeEach -> chain = buildChain() ; chain.add fn1
-
-  it 'should remove fn1 from array', ->
-    chain.remove fn1
+  beforeEach ->
+    chain = buildChain()
+    chain.add fn1
+
+  it 'should return an error when chain is empty', ->
+    chain.array = []
+    result = chain.remove 0
+    assert.equal result.error, 'Invalid index: 0'
+
+  it 'should return a false result when id isnt found', ->
+    chain.array = []
+    result = chain.remove 'id'
+    assert.equal result.result, false
+    assert.equal result.reason, 'not found'
+
+  it 'should return a false result when fn isnt found', ->
+    chain.array = []
+    result = chain.remove fn1
+    assert.equal result.result, false
+    assert.equal result.reason, 'not found'
+
+  it 'should remove fn1 from array by itself', ->
+    result = chain.remove fn1
     assert.equal chain.array.length, 0, 'array should be empty'
-
-  it 'should remove fn1 from array', ->
+    assert.strictEqual result.removed.length, 1, 'should remove one function'
+    assert.strictEqual result.removed?[0], fn1
+
+  it 'should remove fn1 from array by itself', ->
     chain.add fn2, fn3
-    chain.remove fn1
+    result = chain.remove fn1
     assert.equal chain.array.length, 2, 'array should be one less: 2'
     assert.strictEqual chain.array[0], fn2, 'first element should be fn2'
-    assert.strictEqual chain.array[1], fn3, 'first element should be fn3'
-
-  it 'should remove fn2 from array', ->
+    assert.strictEqual chain.array[1], fn3, 'second element should be fn3'
+    assert.strictEqual result.removed.length, 1, 'should remove one function'
+    assert.strictEqual result.removed?[0], fn1
+
+  it 'should remove fn2 from array by itself', ->
     chain.add fn2, fn3
-    chain.remove fn2
+    result = chain.remove fn2
     assert.equal chain.array.length, 2, 'array should be one less: 2'
     assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
-    assert.strictEqual chain.array[1], fn3, 'first element should be fn3'
-
-  it 'should remove fn3 from array', ->
+    assert.strictEqual chain.array[1], fn3, 'second element should be fn3'
+    assert.strictEqual result.removed.length, 1, 'should remove one function'
+    assert.strictEqual result.removed?[0], fn2
+
+  it 'should remove fn3 from array by itself', ->
     chain.add fn2, fn3
-    chain.remove fn3
+    result = chain.remove fn3
     assert.equal chain.array.length, 2, 'array should be one less: 2'
     assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
-    assert.strictEqual chain.array[1], fn2, 'first element should be fn2'
-
-# 4. chain.run()
+    assert.strictEqual chain.array[1], fn2, 'second element should be fn2'
+    assert.strictEqual result.removed.length, 1, 'should remove one function'
+    assert.strictEqual result.removed?[0], fn3
+
+
+  it 'should remove fn1 from array by ID', ->
+    result = chain.remove 'fn1'
+    assert.equal chain.array.length, 0, 'array should be empty'
+    assert.strictEqual result.removed.length, 1, 'should remove one function'
+    assert.strictEqual result.removed?[0], fn1
+
+  it 'should remove fn1 from array by ID', ->
+    chain.add fn2, fn3
+    chain.remove 'fn1'
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn2, 'first element should be fn2'
+    assert.strictEqual chain.array[1], fn3, 'second element should be fn3'
+
+  it 'should remove fn2 from array by ID', ->
+    chain.add fn2, fn3
+    result = chain.remove 'fn2'
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
+    assert.strictEqual chain.array[1], fn3, 'second element should be fn3'
+
+  it 'should remove fn3 from array by ID', ->
+    chain.add fn2, fn3
+    chain.remove 'fn3'
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
+    assert.strictEqual chain.array[1], fn2, 'second element should be fn2'
+
+
+  it 'should remove fn1 from array by index', ->
+    result = chain.remove 0
+    assert.equal chain.array.length, 0, 'array should be empty'
+    assert.strictEqual result.removed.length, 1, 'should remove one function'
+    assert.strictEqual result.removed?[0], fn1
+
+  it 'should remove fn1 from array by index', ->
+    chain.add fn2, fn3
+    chain.remove 0
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn2, 'first element should be fn2'
+    assert.strictEqual chain.array[1], fn3, 'second element should be fn3'
+
+  it 'should remove fn2 from array by index', ->
+    chain.add fn2, fn3
+    result = chain.remove 'fn2'
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
+    assert.strictEqual chain.array[1], fn3, 'second element should be fn3'
+
+  it 'should remove fn3 from array by index', ->
+    chain.add fn2, fn3
+    chain.remove 'fn3'
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
+    assert.strictEqual chain.array[1], fn2, 'second element should be fn2'
+
+
+  it 'should remove via control', ->
+    remover = (control) -> control.remove()
+    chain.add remover, fn2
+    assert.equal chain.array.length, 3, 'array should have 3'
+    chain.run()
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
+    assert.strictEqual chain.array[1], fn2, 'second element should be fn2'
+
+  it 'should remove via control', ->
+    remover = (control) -> control.remove()
+    chain.add remover, fn2
+    assert.equal chain.array.length, 3, 'array should have 3'
+    chain.run()
+    assert.equal chain.array.length, 2, 'array should be one less: 2'
+    assert.strictEqual chain.array[0], fn1, 'first element should be fn1'
+    assert.strictEqual chain.array[1], fn2, 'second element should be fn2'
+
+
+
+# 4. chain.clear()
+describe 'test chain.clear()', ->
+  fn1 = ->
+  fn1.options = id:'fn1'
+  fn2 = ->
+  fn2.options = id:'fn2'
+  fn3 = ->
+  fn3.options = id:'fn3'
+  chain = null
+  beforeEach ->
+    chain = buildChain()
+    #; chain.add fn1, fn2, fn3
+
+  it 'should remove nothing from empty chain', ->
+    assert.equal chain.array.length, 0, 'array should be empty before clear()'
+    result = chain.clear()
+    assert.equal chain.array.length, 0, 'array should be empty'
+    assert.equal result.result, false
+    assert.equal result.reason, 'chain empty'
+    assert.strictEqual result.removed, undefined, 'result shouldnt have any removed'
+
+  it 'should remove function from chain', ->
+    chain.array = [ fn1 ]
+    result = chain.clear()
+    assert.equal chain.array.length, 0, 'array should be empty'
+    assert.strictEqual result.removed.length, 1, 'should remove three functions'
+    assert.strictEqual result.removed?[0], fn1
+
+  it 'should remove all functions from chain', ->
+    chain.array = [ fn1, fn2, fn3 ]
+    result = chain.clear()
+    assert.equal chain.array.length, 0, 'array should be empty'
+    assert.strictEqual result.removed.length, 3, 'should remove three functions'
+    assert.strictEqual result.removed?[0], fn1
+    assert.strictEqual result.removed?[1], fn2
+    assert.strictEqual result.removed?[2], fn3
+
+
+
+# 5. chain.run()
 describe 'test running chain', ->
   ran  = (control, context) -> context.ran = true
   stop = (control, context) -> context.stop = true ; control.stop 'testing'
@@ -132,7 +285,8 @@
     process.nextTick resume

   chain = null
-  beforeEach -> chain = buildChain()
+  beforeEach ->
+    chain = buildChain()

   it 'should catch thrown errors', ->
     chain.add -> throw new Error 'my bad'
@@ -245,7 +399,8 @@
   # TODO: change synch stuff above to *not* call next, move those down here.
   describe 'asynch\'ly', ->
     unpaused = -> this.paused = false
-    beforeEach -> chain.add pause, unpaused, ran
+    beforeEach ->
+      chain.add pause, unpaused, ran

     it 'without specifying a context should use default context and return it', (done) ->
       syncResult = chain.run done: (error, results) ->
@@ -381,3 +536,433 @@
         assert.equal result?.context?.paused, false, 'should have called last fn and set paused=false'
         assert.deepEqual syncResult?.paused, reason:'testing', index:0, fn:pause
         done()
+
+
+
+
+# 6. disable
+# - chain.disable()
+# - chain.disable('reason')
+#
+# - chain.disable(index)
+# - chain.disable(index, 'reason')
+#
+# - chain.disable('someid')
+# - chain.disable('someid', 'reason')
+#
+# - chain.disable(someFn)
+# - chain.disable(someFn, 'reason')
+#
+# - control.disable()
+# - control.disable('reason')
+#
+# - select(fn).disable()        - single
+# - select(fn).disable(reason)  - single
+#
+# - select(fn).disable()        - duo
+# - select(fn).disable(reason)  - duo
+
+
+describe 'test disable', ->
+
+  fn1 = ->
+  fn1.options = id:'fn1'
+  fn2 = ->
+  fn2.options = id:'fn2'
+  fn3 = ->
+  fn3.options = id:'fn3'
+
+  chain = null
+
+  beforeEach ->
+    chain = buildChain()
+
+  it 'should disable the chain with a default reason', ->
+
+    called = []
+    caller1 = -> called.push 1
+    caller1.options = id:'caller1'
+    caller2 = -> called.push 2
+    caller2.options = id:'caller2'
+    caller3 = -> called.push 3
+    caller3.options = id:'caller3'
+    chain.add caller1, caller2, caller3
+
+    results1 = chain.run()
+    results2 = chain.disable()
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert results2.reason, 'disable reason should default to true'
+    assert.equal results3.result, false, 'second run should fail due to being disabled'
+    assert.equal results3.reason, 'chain disabled'
+    assert.equal results3.disabled, true
+    assert.equal called.length, 3, 'chain should be called only once (for 3 functions)'
+    assert.deepEqual called, [ 1, 2, 3]
+
+  it 'should disable the chain with a specified reason', ->
+
+    reason = 'the reason'
+    called = []
+    caller1 = -> called.push 1
+    caller1.options = id:'caller1'
+    caller2 = -> called.push 2
+    caller2.options = id:'caller2'
+    caller3 = -> called.push 3
+    caller3.options = id:'caller3'
+    chain.add caller1, caller2, caller3
+
+    results1 = chain.run()
+    results2 = chain.disable reason
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert results2.reason, 'disable reason should default to true'
+    assert.equal results3.result, false, 'second run should fail due to being disabled'
+    assert.equal results3.reason, 'chain disabled'
+    assert.equal results3.disabled, reason
+    assert.equal called.length, 3, 'chain should be called only once (for 3 functions)'
+    assert.deepEqual called, [ 1, 2, 3]
+
+
+  it 'should disable the function by index with default reason', ->
+
+    called = []
+    caller = -> called.push true
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.disable 1
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert results2.reason, 'disable reason should default to true'
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+
+  it 'should disable the function by index with specific reason', ->
+    reason = 'the reason'
+    called = []
+    caller = -> called.push true
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.disable 1, reason
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert.equal results2.reason, reason
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+
+
+  # NOTE: you can't use default reason with an id.
+
+  it 'should disable the function by id with specific reason', ->
+    reason = 'the reason'
+    called = []
+    caller = -> called.push true
+    caller.options = id:'caller'
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.disable 'caller', reason
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert.equal results2.reason, reason
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+
+
+  it 'should disable the function by function with default reason', ->
+
+    called = []
+    caller = -> called.push true
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.disable caller
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert results2.reason, 'disable reason should default to true'
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+
+  it 'should disable the function by function with specific reason', ->
+    reason = 'the reason'
+    called = []
+    caller = -> called.push true
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.disable caller, reason
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'disable should succeed'
+    assert.equal results2.reason, reason
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+
+
+  it 'should disable via control with default reason', ->
+    disabler = (control) -> control.disable()
+    chain.add fn1, disabler, fn3
+    assert.equal chain.array.length, 3, 'should start with 3 functions'
+    chain.run()
+    assert.equal chain.array.length, 3, 'should still have 3 functions'
+    assert.equal chain.array[1].options.disabled, true
+
+  it 'should disable via control with specific reason', ->
+    reason = 'the reason'
+    disabler = (control) -> control.disable reason
+    chain.add fn1, disabler, fn3
+    assert.equal chain.array.length, 3, 'should start with 3 functions'
+    chain.run()
+    assert.equal chain.array.length, 3, 'should still have 3 functions'
+    assert.equal chain.array[1].options.disabled, reason
+
+
+  it 'should select() a function and disable with default reason', ->
+    target = ->
+    selector = (fn, index) -> (fn is target and index is 1)
+    chain.add fn1, target, fn3
+    assert.equal chain.array.length, 3, 'should start with 3 functions'
+    chain.select(selector).disable()
+    assert.equal chain.array.length, 3, 'should still have 3 functions'
+    assert.equal target.options.disabled, true
+    assert.equal chain.array[1].options.disabled, true
+
+  it 'should select() a function and disable with specific reason', ->
+    reason = 'the reason'
+    target = ->
+    selector = (fn, index) -> (fn is target and index is 1)
+    chain.add fn1, target, fn3
+    assert.equal chain.array.length, 3, 'should start with 3 functions'
+    chain.select(selector).disable reason
+    assert.equal chain.array.length, 3, 'should still have 3 functions'
+    assert.equal target.options.disabled, reason
+    assert.equal chain.array[1].options.disabled, reason
+
+
+  it 'should select() two functions and disable with default reason', ->
+    reason = true
+    target1 = ->
+    target2 = ->
+    selector = (fn, index) ->
+      (fn is target1 and index is 1) or (fn is target2 and index is 2)
+    chain.add fn1, target1, target2, fn3
+    assert.equal chain.array.length, 4, 'should start with 3 functions'
+    chain.select(selector).disable()
+    assert.equal chain.array.length, 4, 'should still have 3 functions'
+    assert.equal target1.options.disabled, reason
+    assert.equal target2.options.disabled, reason
+    assert.equal chain.array[1].options.disabled, reason
+    assert.equal chain.array[2].options.disabled, reason
+
+  it 'should select() two functions and disable with specific reason', ->
+    reason = 'the reason'
+    target1 = ->
+    target2 = ->
+    selector = (fn, index) ->
+      (fn is target1 and index is 1) or (fn is target2 and index is 2)
+    chain.add fn1, target1, target2, fn3
+    assert.equal chain.array.length, 4, 'should start with 3 functions'
+    chain.select(selector).disable reason
+    assert.equal chain.array.length, 4, 'should still have 3 functions'
+    assert.equal target1.options.disabled, reason
+    assert.equal target2.options.disabled, reason
+    assert.equal chain.array[1].options.disabled, reason
+    assert.equal chain.array[2].options.disabled, reason
+
+
+
+
+# 7. enable
+# - chain.enable()
+# - chain.enable(index)
+# - chain.enable('someid')
+# - chain.enable(someFn)
+# - select(fn).enable()        - single
+# - select(fn).enable()        - duo
+
+describe 'test enable', ->
+
+  fn1 = ->
+  fn1.options = id:'fn1'
+  fn2 = ->
+  fn2.options = id:'fn2'
+  fn3 = ->
+  fn3.options = id:'fn3'
+
+  chain = null
+
+  beforeEach ->
+    chain = buildChain()
+
+  it 'should have false result if chain is already enabled', ->
+
+    results = chain.enable()
+
+    assert.equal results.result, false, 'should fail because it\'s already enabled'
+    assert.equal results.reason, 'chain not disabled'
+
+  it 'should enable the chain', ->
+
+    called = []
+    caller1 = -> called.push 1
+    caller1.options = id:'caller1'
+    caller2 = -> called.push 2
+    caller2.options = id:'caller2'
+    caller3 = -> called.push 3
+    caller3.options = id:'caller3'
+    chain.add caller1, caller2, caller3
+
+    chain._disabled = true
+    results1 = chain.run()
+    results2 = chain.enable()
+    results3 = chain.run()
+
+    assert.equal results1.result, false, 'first run should fail because it\'s disabled'
+    assert results2.result, 'enable should succeed'
+    assert results3.result, 'second run should succeed due to being enabled'
+    assert.equal results3.disabled, undefined
+    assert.equal called.length, 3, 'chain should be called only once (for 3 functions)'
+    assert.deepEqual called, [ 1, 2, 3]
+
+
+  it 'should have false result if function is already enabled', ->
+
+    chain.add fn1
+    results = chain.enable 0
+
+    assert.equal results.result, false, 'should fail because it\'s already enabled'
+    assert.equal results.reason, 'function not disabled'
+
+  it 'should enable the function by index', ->
+
+    called = []
+    caller = -> called.push true
+    caller.options = disabled:true
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.enable 1
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'enable should succeed'
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+    assert.equal caller.options.disabled, undefined, 'should delete the `disabled` marker'
+
+
+  it 'should enable the function by id', ->
+    called = []
+    caller = -> called.push true
+    caller.options = disabled:true, id:'caller'
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.enable 'caller'
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'enable should succeed'
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+    assert.equal caller.options.disabled, undefined, 'should delete the `disabled` marker'
+    assert.equal caller.options.id, 'caller', 'should still have the id in options'
+
+
+  it 'should enable the function by function', ->
+
+    called = []
+    caller = -> called.push true
+    caller.options = disabled:true
+    chain.add fn1, caller, fn3
+
+    results1 = chain.run()
+    results2 = chain.enable caller
+    results3 = chain.run()
+
+    assert results1.result, 'first run should succeed'
+    assert results2.result, 'enable should succeed'
+    assert results3.result, 'second run should succeed'
+    assert.equal called.length, 1, 'should be called only once'
+    assert.equal caller.options.disabled, undefined, 'should delete the `disabled` marker'
+
+
+
+  it 'should select() a function and enable', ->
+
+    target = ->
+    target.options = disabled:true
+    selector = (fn, index) -> (fn is target and index is 1)
+    chain.add fn1, target, fn3
+    assert.equal chain.array.length, 3, 'should start with 3 functions'
+    chain.select(selector).enable()
+    assert.equal chain.array.length, 3, 'should still have 3 functions'
+    assert.equal target.options.disabled, undefined
+    assert.equal chain.array[1].options.disabled, undefined
+
+
+  it 'should select() two functions and enable', ->
+
+    target1 = ->
+    target1.options = disabled:true
+    target2 = ->
+    target2.options = disabled:true
+    selector = (fn, index) ->
+      (fn is target1 and index is 1) or (fn is target2 and index is 2)
+    chain.add fn1, target1, target2, fn3
+    assert.equal chain.array.length, 4, 'should start with 3 functions'
+    chain.select(selector).enable()
+    assert.equal chain.array.length, 4, 'should still have 3 functions'
+    assert.equal target1.options.disabled, undefined
+    assert.equal target2.options.disabled, undefined
+    assert.equal chain.array[1].options.disabled, undefined
+    assert.equal chain.array[2].options.disabled, undefined
+
+
+
+
+  # TODO: Move these to testing for .select(...).remove()
+  # it 'should remove zero functions with falsey selector', ->
+  #   chain.array = [ fn1, fn2, fn3 ]
+  #   result = chain.clear -> false
+  #   assert.equal chain.array.length, 3, 'array should be unchanged'
+  #   assert.strictEqual result.removed.length, 0
+  #
+  # it 'should remove all functions from with truthy selector', ->
+  #   chain.array = [ fn1, fn2, fn3 ]
+  #   result = chain.clear -> true
+  #   assert.equal chain.array.length, 0, 'array should be empty'
+  #   assert.strictEqual result.removed.length, 3
+  #   assert.strictEqual result.removed?[0], fn1
+  #   assert.strictEqual result.removed?[1], fn2
+  #   assert.strictEqual result.removed?[2], fn3
+  #
+  #
+  # it 'should remove function from chain by id selector', ->
+  #   chain.array = [ fn1, fn2, fn3 ]
+  #   result = chain.clear (fn) -> fn?.options?.id is 'fn2'
+  #   assert.equal chain.array.length, 2, 'array should be one less'
+  #   assert.strictEqual result.removed.length, 1, 'should remove one function'
+  #   assert.strictEqual result.removed?[0], fn2
+  #
+  # it 'should remove functions from chain by two-id selector', ->
+  #   chain.array = [ fn1, fn2, fn3, (->) ]
+  #   result = chain.clear (fn) -> fn?.options?.id is 'fn2' or fn?.options?.id is 'fn3'
+  #   assert.equal chain.array.length, 2, 'array should be two less'
+  #   assert.strictEqual result.removed.length, 2, 'should remove two functions'
+  #   assert.strictEqual result.removed?[0], fn2
+  #   assert.strictEqual result.removed?[1], fn3


performance update - draw time to 25%

performance update - draw time to 25%

--- coffee/plasmid.coffee
+++ coffee/plasmid.coffee
@@ -96,10 +96,13 @@
 		@ctx.scale(@multiplier, @multiplier)

 	render: ->
+		@ctx.fillStyle = @color[0]
+		@ctx.fillRect(0, 0, @col, @row)
+		@ctx.fillStyle = @color[1]
 		for i in [1..@col]
 			for j in [1..@row]
-				@ctx.fillStyle = @color[@cells[i][j]]
-				@ctx.fillRect(i - 1, j - 1, 1, 1)
+				if @cells[i][j] is 1
+					@ctx.fillRect(i - 1, j - 1, 1, 1)

 	drawCell: (x, y)->
 		@ctx.fillStyle = @color[@cells[x][y]]


Don't show tooltip after click if leads to another link

--- coffee/tooltip.coffee
+++ coffee/tooltip.coffee
@@ -80,4 +80,8 @@
   # Select new dot as current
   selectedLi.addClass 'current'

+  if dot.attr('href')
+    selectedLi.removeClass 'current'
+    return
+
   return


added svg generation, but not exposed in ui

--- Skuler.coffee
+++ Skuler.coffee
@@ -274,6 +274,25 @@

 		@ # do not accumulate and return loop results (just return this)

+	getSVG: ->
+		lines = for strokeInfo in @strokes
+			[index, saturation, lightness, stroke] = strokeInfo
+			color = new SkulerColor @getColorAt(index).base, saturation, lightness
+
+			hex = Utils.toCSSHex color.calculated
+			points = stroke.join(",")
+			'<polyline stroke="'+hex+'" points="'+points+'" />\n'
+
+		canv = @context.canvas
+		w = canv.width
+		h = canv.height
+
+		'<?xml version="1.0"?>\n' +
+			'<svg width="'+w+'" height="'+h+'" viewPort="0 0 '+w+' '+h+'" ' +
+			'version="1.1" xmlns="http://www.w3.org/2000/svg">\n' +
+			'<g fill="none" stroke-linejoin="round" stroke-linecap="round" stroke-width="'+@context.lineWidth+'">\n' +
+			lines +
+			'</g>\n</svg>'

 class SkulerSwatch


fix(onboarding): Focus first field when showing settings #1696

--- internal_packages/onboarding/lib/account-settings-page.cjsx
+++ internal_packages/onboarding/lib/account-settings-page.cjsx
@@ -81,6 +81,19 @@
       </form>
     </div>

+  componentDidMount: =>
+    @_applyFocus()
+
+  componentDidUpdate: =>
+    @_applyFocus()
+
+  _applyFocus: =>
+    firstInput = React.findDOMNode(@).querySelector('input')
+    anyInputFocused = document.activeElement and document.activeElement.nodeName is 'INPUT'
+
+    if firstInput and not anyInputFocused
+      firstInput.focus()
+
   _onSettingsChanged: (event) =>
     # NOTE: This code is largely duplicated in _onValueChanged. TODO Fix!
     {field, format} = event.target.dataset


Fix task spec.

Fix task spec.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -113,7 +113,7 @@
                     sauceUser: process.env.SAUCE_USERNAME
                     sauceKey: process.env.SAUCE_ACCESS_KEY
         }
-        browserTasks.push('ci_' + index)
+        browserTasks.push('protractor:ci_' + index)
     @config('protractor', protractorConfig)
     @registerTask 'ci_saucelabs', browserTasks


fix(grunt): cached Jekyll metadata not purged

This will cause unexpected result for switching between two or more Jekyll build configurations because the files are stay untouched since last build.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -263,6 +263,12 @@
       dist:
         src: [
           ".tmp"
+        ]
+
+      jekyllMetadata:
+        src: [
+          "<%= config.dist %>"
+          "<%= config.app %>/.jekyll-metadata"
         ]

       postDist:


Improvement: abort grunt on compression failure

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -62,6 +62,7 @@
           'build/js/loader/loader_common.min.js':       ['build/js/loader/loader_common.js']
     shell:
       gzip_js_files:
+        options: failOnError: true
         command: compress_command('build/js/loader/loader_common.min.js')
     copy:
       dist_loader_aliases:


Lint Gruntfile.coffee

Lint Gruntfile.coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -19,6 +19,7 @@

       src: ['src/**/*.coffee']
       test: ['spec/*.coffee']
+      gruntfile: ['Gruntfile.coffee']

     shell:
       test:
@@ -35,7 +36,7 @@
   grunt.registerTask 'clean', ->
     grunt.file.delete('lib') if grunt.file.exists('lib')

-  grunt.registerTask('lint', ['coffeelint:src', 'coffeelint:test'])
+  grunt.registerTask('lint', ['coffeelint'])
   grunt.registerTask('default', ['coffee', 'coffeelint:src'])
   grunt.registerTask('test', ['clean', 'default', 'coffeelint:test', 'shell:test'])
   grunt.registerTask('prepublish', ['clean', 'coffee', 'lint'])


fix(Gruntfile.coffee): Indentation-o

fix(Gruntfile.coffee): Indentation-o

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -735,7 +735,7 @@
                         grunt.option('browsers', [test_type])
                 else
                     grunt.option('browsers', ['Chrome'])
-                    taskList.push('tests:local')
+                taskList.push('tests:local')

         grunt.task.run(taskList)


adding jscs to clean and validate tasks

adding jscs to clean and validate tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -178,12 +178,14 @@
   grunt.registerTask "clean", [
     "jsbeautifier:modify"
     "jshint"
+    "jscs"
   ]

   # Validate code before commit (read only)
   grunt.registerTask "validate", [
     "jsbeautifier:validate"
     "jshint"
+    "jscs"
   ]

   # Build for Production


Remove print-pdf

Remove print-pdf

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -111,6 +111,8 @@
                       'bower_components/reveal.js/test/test.js'
                       'bower_components/reveal.js/plugin/math'
                       'bower_components/reveal.js/plugin/math/math.js'
+                      'bower_components/reveal.js/plugin/print-pdf'
+                      'bower_components/reveal.js/plugin/print-pdf/print-pdf.js'
                     ]
                 src: [
                     'css/**'