bump Octicons to 2.4.1

bump Octicons to 2.4.1

--- snippets/octicon.cson
+++ snippets/octicon.cson
@@ -1,613 +1,619 @@
 '.text.html':
   'octicon-alert':
     'prefix': 'octicon-alert'
-    'body': '<${1:span} class="octicon octicon-alert">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-alert$2">$3</${1:span}>'
   'octicon-alignment-align':
     'prefix': 'octicon-alignment-align'
-    'body': '<${1:span} class="octicon octicon-alignment-align">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-alignment-align$2">$3</${1:span}>'
   'octicon-alignment-aligned-to':
     'prefix': 'octicon-alignment-aligned-to'
-    'body': '<${1:span} class="octicon octicon-alignment-aligned-to">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-alignment-aligned-to$2">$3</${1:span}>'
   'octicon-alignment-unalign':
     'prefix': 'octicon-alignment-unalign'
-    'body': '<${1:span} class="octicon octicon-alignment-unalign">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-alignment-unalign$2">$3</${1:span}>'
   'octicon-arrow-down':
     'prefix': 'octicon-arrow-down'
-    'body': '<${1:span} class="octicon octicon-arrow-down">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-down$2">$3</${1:span}>'
   'octicon-arrow-left':
     'prefix': 'octicon-arrow-left'
-    'body': '<${1:span} class="octicon octicon-arrow-left">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-left$2">$3</${1:span}>'
   'octicon-arrow-right':
     'prefix': 'octicon-arrow-right'
-    'body': '<${1:span} class="octicon octicon-arrow-right">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-right$2">$3</${1:span}>'
   'octicon-arrow-small-down':
     'prefix': 'octicon-arrow-small-down'
-    'body': '<${1:span} class="octicon octicon-arrow-small-down">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-small-down$2">$3</${1:span}>'
   'octicon-arrow-small-left':
     'prefix': 'octicon-arrow-small-left'
-    'body': '<${1:span} class="octicon octicon-arrow-small-left">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-small-left$2">$3</${1:span}>'
   'octicon-arrow-small-right':
     'prefix': 'octicon-arrow-small-right'
-    'body': '<${1:span} class="octicon octicon-arrow-small-right">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-small-right$2">$3</${1:span}>'
   'octicon-arrow-small-up':
     'prefix': 'octicon-arrow-small-up'
-    'body': '<${1:span} class="octicon octicon-arrow-small-up">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-small-up$2">$3</${1:span}>'
   'octicon-arrow-up':
     'prefix': 'octicon-arrow-up'
-    'body': '<${1:span} class="octicon octicon-arrow-up">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-arrow-up$2">$3</${1:span}>'
   'octicon-beer':
     'prefix': 'octicon-beer'
-    'body': '<${1:span} class="octicon octicon-beer">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-beer$2">$3</${1:span}>'
   'octicon-book':
     'prefix': 'octicon-book'
-    'body': '<${1:span} class="octicon octicon-book">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-book$2">$3</${1:span}>'
   'octicon-bookmark':
     'prefix': 'octicon-bookmark'
-    'body': '<${1:span} class="octicon octicon-bookmark">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-bookmark$2">$3</${1:span}>'
   'octicon-briefcase':
     'prefix': 'octicon-briefcase'
-    'body': '<${1:span} class="octicon octicon-briefcase">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-briefcase$2">$3</${1:span}>'
   'octicon-broadcast':
     'prefix': 'octicon-broadcast'
-    'body': '<${1:span} class="octicon octicon-broadcast">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-broadcast$2">$3</${1:span}>'
   'octicon-browser':
     'prefix': 'octicon-browser'
-    'body': '<${1:span} class="octicon octicon-browser">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-browser$2">$3</${1:span}>'
   'octicon-bug':
     'prefix': 'octicon-bug'
-    'body': '<${1:span} class="octicon octicon-bug">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-bug$2">$3</${1:span}>'
   'octicon-calendar':
     'prefix': 'octicon-calendar'
-    'body': '<${1:span} class="octicon octicon-calendar">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-calendar$2">$3</${1:span}>'
   'octicon-check':
     'prefix': 'octicon-check'
-    'body': '<${1:span} class="octicon octicon-check">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-check$2">$3</${1:span}>'
   'octicon-checklist':
     'prefix': 'octicon-checklist'
-    'body': '<${1:span} class="octicon octicon-checklist">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-checklist$2">$3</${1:span}>'
   'octicon-chevron-down':
     'prefix': 'octicon-chevron-down'
-    'body': '<${1:span} class="octicon octicon-chevron-down">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-chevron-down$2">$3</${1:span}>'
   'octicon-chevron-left':
     'prefix': 'octicon-chevron-left'
-    'body': '<${1:span} class="octicon octicon-chevron-left">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-chevron-left$2">$3</${1:span}>'
   'octicon-chevron-right':
     'prefix': 'octicon-chevron-right'
-    'body': '<${1:span} class="octicon octicon-chevron-right">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-chevron-right$2">$3</${1:span}>'
   'octicon-chevron-up':
     'prefix': 'octicon-chevron-up'
-    'body': '<${1:span} class="octicon octicon-chevron-up">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-chevron-up$2">$3</${1:span}>'
   'octicon-circle-slash':
     'prefix': 'octicon-circle-slash'
-    'body': '<${1:span} class="octicon octicon-circle-slash">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-circle-slash$2">$3</${1:span}>'
   'octicon-circuit-board':
     'prefix': 'octicon-circuit-board'
-    'body': '<${1:span} class="octicon octicon-circuit-board">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-circuit-board$2">$3</${1:span}>'
   'octicon-clippy':
     'prefix': 'octicon-clippy'
-    'body': '<${1:span} class="octicon octicon-clippy">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-clippy$2">$3</${1:span}>'
   'octicon-clock':
     'prefix': 'octicon-clock'
-    'body': '<${1:span} class="octicon octicon-clock">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-clock$2">$3</${1:span}>'
   'octicon-cloud-download':
     'prefix': 'octicon-cloud-download'
-    'body': '<${1:span} class="octicon octicon-cloud-download">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-cloud-download$2">$3</${1:span}>'
   'octicon-cloud-upload':
     'prefix': 'octicon-cloud-upload'
-    'body': '<${1:span} class="octicon octicon-cloud-upload">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-cloud-upload$2">$3</${1:span}>'
   'octicon-code':
     'prefix': 'octicon-code'
-    'body': '<${1:span} class="octicon octicon-code">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-code$2">$3</${1:span}>'
   'octicon-color-mode':
     'prefix': 'octicon-color-mode'
-    'body': '<${1:span} class="octicon octicon-color-mode">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-color-mode$2">$3</${1:span}>'
   'octicon-comment-add':
     'prefix': 'octicon-comment-add'
-    'body': '<${1:span} class="octicon octicon-comment-add">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-comment-add$2">$3</${1:span}>'
   'octicon-comment-discussion':
     'prefix': 'octicon-comment-discussion'
-    'body': '<${1:span} class="octicon octicon-comment-discussion">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-comment-discussion$2">$3</${1:span}>'
   'octicon-comment':
     'prefix': 'octicon-comment'
-    'body': '<${1:span} class="octicon octicon-comment">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-comment$2">$3</${1:span}>'
   'octicon-credit-card':
     'prefix': 'octicon-credit-card'
-    'body': '<${1:span} class="octicon octicon-credit-card">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-credit-card$2">$3</${1:span}>'
   'octicon-dash':
     'prefix': 'octicon-dash'
-    'body': '<${1:span} class="octicon octicon-dash">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-dash$2">$3</${1:span}>'
   'octicon-dashboard':
     'prefix': 'octicon-dashboard'
-    'body': '<${1:span} class="octicon octicon-dashboard">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-dashboard$2">$3</${1:span}>'
   'octicon-database':
     'prefix': 'octicon-database'
-    'body': '<${1:span} class="octicon octicon-database">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-database$2">$3</${1:span}>'
   'octicon-device-camera-video':
     'prefix': 'octicon-device-camera-video'
-    'body': '<${1:span} class="octicon octicon-device-camera-video">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-device-camera-video$2">$3</${1:span}>'
   'octicon-device-camera':
     'prefix': 'octicon-device-camera'
-    'body': '<${1:span} class="octicon octicon-device-camera">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-device-camera$2">$3</${1:span}>'
   'octicon-device-desktop':
     'prefix': 'octicon-device-desktop'
-    'body': '<${1:span} class="octicon octicon-device-desktop">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-device-desktop$2">$3</${1:span}>'
   'octicon-device-mobile':
     'prefix': 'octicon-device-mobile'
-    'body': '<${1:span} class="octicon octicon-device-mobile">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-device-mobile$2">$3</${1:span}>'
   'octicon-diff-added':
     'prefix': 'octicon-diff-added'
-    'body': '<${1:span} class="octicon octicon-diff-added">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-diff-added$2">$3</${1:span}>'
   'octicon-diff-ignored':
     'prefix': 'octicon-diff-ignored'
-    'body': '<${1:span} class="octicon octicon-diff-ignored">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-diff-ignored$2">$3</${1:span}>'
   'octicon-diff-modified':
     'prefix': 'octicon-diff-modified'
-    'body': '<${1:span} class="octicon octicon-diff-modified">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-diff-modified$2">$3</${1:span}>'
   'octicon-diff-removed':
     'prefix': 'octicon-diff-removed'
-    'body': '<${1:span} class="octicon octicon-diff-removed">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-diff-removed$2">$3</${1:span}>'
   'octicon-diff-renamed':
     'prefix': 'octicon-diff-renamed'
-    'body': '<${1:span} class="octicon octicon-diff-renamed">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-diff-renamed$2">$3</${1:span}>'
   'octicon-diff':
     'prefix': 'octicon-diff'
-    'body': '<${1:span} class="octicon octicon-diff">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-diff$2">$3</${1:span}>'
   'octicon-ellipsis':
     'prefix': 'octicon-ellipsis'
-    'body': '<${1:span} class="octicon octicon-ellipsis">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-ellipsis$2">$3</${1:span}>'
   'octicon-eye-unwatch':
     'prefix': 'octicon-eye-unwatch'
-    'body': '<${1:span} class="octicon octicon-eye-unwatch">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-eye-unwatch$2">$3</${1:span}>'
   'octicon-eye-watch':
     'prefix': 'octicon-eye-watch'
-    'body': '<${1:span} class="octicon octicon-eye-watch">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-eye-watch$2">$3</${1:span}>'
   'octicon-eye':
     'prefix': 'octicon-eye'
-    'body': '<${1:span} class="octicon octicon-eye">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-eye$2">$3</${1:span}>'
   'octicon-file-add':
     'prefix': 'octicon-file-add'
-    'body': '<${1:span} class="octicon octicon-file-add">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-add$2">$3</${1:span}>'
   'octicon-file-binary':
     'prefix': 'octicon-file-binary'
-    'body': '<${1:span} class="octicon octicon-file-binary">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-binary$2">$3</${1:span}>'
   'octicon-file-code':
     'prefix': 'octicon-file-code'
-    'body': '<${1:span} class="octicon octicon-file-code">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-code$2">$3</${1:span}>'
   'octicon-file-directory-create':
     'prefix': 'octicon-file-directory-create'
-    'body': '<${1:span} class="octicon octicon-file-directory-create">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-directory-create$2">$3</${1:span}>'
   'octicon-file-directory':
     'prefix': 'octicon-file-directory'
-    'body': '<${1:span} class="octicon octicon-file-directory">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-directory$2">$3</${1:span}>'
   'octicon-file-media':
     'prefix': 'octicon-file-media'
-    'body': '<${1:span} class="octicon octicon-file-media">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-media$2">$3</${1:span}>'
   'octicon-file-pdf':
     'prefix': 'octicon-file-pdf'
-    'body': '<${1:span} class="octicon octicon-file-pdf">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-pdf$2">$3</${1:span}>'
   'octicon-file-submodule':
     'prefix': 'octicon-file-submodule'
-    'body': '<${1:span} class="octicon octicon-file-submodule">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-submodule$2">$3</${1:span}>'
   'octicon-file-symlink-directory':
     'prefix': 'octicon-file-symlink-directory'
-    'body': '<${1:span} class="octicon octicon-file-symlink-directory">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-symlink-directory$2">$3</${1:span}>'
   'octicon-file-symlink-file':
     'prefix': 'octicon-file-symlink-file'
-    'body': '<${1:span} class="octicon octicon-file-symlink-file">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-symlink-file$2">$3</${1:span}>'
   'octicon-file-text':
     'prefix': 'octicon-file-text'
-    'body': '<${1:span} class="octicon octicon-file-text">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-text$2">$3</${1:span}>'
   'octicon-file-zip':
     'prefix': 'octicon-file-zip'
-    'body': '<${1:span} class="octicon octicon-file-zip">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-file-zip$2">$3</${1:span}>'
   'octicon-flame':
     'prefix': 'octicon-flame'
-    'body': '<${1:span} class="octicon octicon-flame">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-flame$2">$3</${1:span}>'
   'octicon-fold':
     'prefix': 'octicon-fold'
-    'body': '<${1:span} class="octicon octicon-fold">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-fold$2">$3</${1:span}>'
   'octicon-gear':
     'prefix': 'octicon-gear'
-    'body': '<${1:span} class="octicon octicon-gear">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gear$2">$3</${1:span}>'
   'octicon-gift':
     'prefix': 'octicon-gift'
-    'body': '<${1:span} class="octicon octicon-gift">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gift$2">$3</${1:span}>'
   'octicon-gist-fork':
     'prefix': 'octicon-gist-fork'
-    'body': '<${1:span} class="octicon octicon-gist-fork">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gist-fork$2">$3</${1:span}>'
   'octicon-gist-new':
     'prefix': 'octicon-gist-new'
-    'body': '<${1:span} class="octicon octicon-gist-new">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gist-new$2">$3</${1:span}>'
   'octicon-gist-private':
     'prefix': 'octicon-gist-private'
-    'body': '<${1:span} class="octicon octicon-gist-private">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gist-private$2">$3</${1:span}>'
   'octicon-gist-secret':
     'prefix': 'octicon-gist-secret'
-    'body': '<${1:span} class="octicon octicon-gist-secret">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gist-secret$2">$3</${1:span}>'
   'octicon-gist':
     'prefix': 'octicon-gist'
-    'body': '<${1:span} class="octicon octicon-gist">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-gist$2">$3</${1:span}>'
   'octicon-git-branch-create':
     'prefix': 'octicon-git-branch-create'
-    'body': '<${1:span} class="octicon octicon-git-branch-create">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-branch-create$2">$3</${1:span}>'
   'octicon-git-branch-delete':
     'prefix': 'octicon-git-branch-delete'
-    'body': '<${1:span} class="octicon octicon-git-branch-delete">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-branch-delete$2">$3</${1:span}>'
   'octicon-git-branch':
     'prefix': 'octicon-git-branch'
-    'body': '<${1:span} class="octicon octicon-git-branch">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-branch$2">$3</${1:span}>'
   'octicon-git-commit':
     'prefix': 'octicon-git-commit'
-    'body': '<${1:span} class="octicon octicon-git-commit">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-commit$2">$3</${1:span}>'
   'octicon-git-compare':
     'prefix': 'octicon-git-compare'
-    'body': '<${1:span} class="octicon octicon-git-compare">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-compare$2">$3</${1:span}>'
   'octicon-git-fork-private':
     'prefix': 'octicon-git-fork-private'
-    'body': '<${1:span} class="octicon octicon-git-fork-private">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-fork-private$2">$3</${1:span}>'
   'octicon-git-merge':
     'prefix': 'octicon-git-merge'
-    'body': '<${1:span} class="octicon octicon-git-merge">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-merge$2">$3</${1:span}>'
   'octicon-git-pull-request-abandoned':
     'prefix': 'octicon-git-pull-request-abandoned'
-    'body': '<${1:span} class="octicon octicon-git-pull-request-abandoned">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-pull-request-abandoned$2">$3</${1:span}>'
   'octicon-git-pull-request':
     'prefix': 'octicon-git-pull-request'
-    'body': '<${1:span} class="octicon octicon-git-pull-request">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-git-pull-request$2">$3</${1:span}>'
   'octicon-globe':
     'prefix': 'octicon-globe'
-    'body': '<${1:span} class="octicon octicon-globe">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-globe$2">$3</${1:span}>'
   'octicon-graph':
     'prefix': 'octicon-graph'
-    'body': '<${1:span} class="octicon octicon-graph">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-graph$2">$3</${1:span}>'
   'octicon-heart':
     'prefix': 'octicon-heart'
-    'body': '<${1:span} class="octicon octicon-heart">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-heart$2">$3</${1:span}>'
   'octicon-history':
     'prefix': 'octicon-history'
-    'body': '<${1:span} class="octicon octicon-history">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-history$2">$3</${1:span}>'
   'octicon-home':
     'prefix': 'octicon-home'
-    'body': '<${1:span} class="octicon octicon-home">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-home$2">$3</${1:span}>'
   'octicon-horizontal-rule':
     'prefix': 'octicon-horizontal-rule'
-    'body': '<${1:span} class="octicon octicon-horizontal-rule">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-horizontal-rule$2">$3</${1:span}>'
   'octicon-hourglass':
     'prefix': 'octicon-hourglass'
-    'body': '<${1:span} class="octicon octicon-hourglass">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-hourglass$2">$3</${1:span}>'
   'octicon-hubot':
     'prefix': 'octicon-hubot'
-    'body': '<${1:span} class="octicon octicon-hubot">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-hubot$2">$3</${1:span}>'
   'octicon-inbox':
     'prefix': 'octicon-inbox'
-    'body': '<${1:span} class="octicon octicon-inbox">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-inbox$2">$3</${1:span}>'
   'octicon-info':
     'prefix': 'octicon-info'
-    'body': '<${1:span} class="octicon octicon-info">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-info$2">$3</${1:span}>'
   'octicon-issue-closed':
     'prefix': 'octicon-issue-closed'
-    'body': '<${1:span} class="octicon octicon-issue-closed">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-issue-closed$2">$3</${1:span}>'
   'octicon-issue-opened':
     'prefix': 'octicon-issue-opened'
-    'body': '<${1:span} class="octicon octicon-issue-opened">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-issue-opened$2">$3</${1:span}>'
   'octicon-issue-reopened':
     'prefix': 'octicon-issue-reopened'
-    'body': '<${1:span} class="octicon octicon-issue-reopened">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-issue-reopened$2">$3</${1:span}>'
   'octicon-jersey':
     'prefix': 'octicon-jersey'
-    'body': '<${1:span} class="octicon octicon-jersey">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-jersey$2">$3</${1:span}>'
   'octicon-jump-down':
     'prefix': 'octicon-jump-down'
-    'body': '<${1:span} class="octicon octicon-jump-down">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-jump-down$2">$3</${1:span}>'
   'octicon-jump-left':
     'prefix': 'octicon-jump-left'
-    'body': '<${1:span} class="octicon octicon-jump-left">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-jump-left$2">$3</${1:span}>'
   'octicon-jump-right':
     'prefix': 'octicon-jump-right'
-    'body': '<${1:span} class="octicon octicon-jump-right">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-jump-right$2">$3</${1:span}>'
   'octicon-jump-up':
     'prefix': 'octicon-jump-up'
-    'body': '<${1:span} class="octicon octicon-jump-up">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-jump-up$2">$3</${1:span}>'
   'octicon-key':
     'prefix': 'octicon-key'
-    'body': '<${1:span} class="octicon octicon-key">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-key$2">$3</${1:span}>'
   'octicon-keyboard':
     'prefix': 'octicon-keyboard'
-    'body': '<${1:span} class="octicon octicon-keyboard">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-keyboard$2">$3</${1:span}>'
   'octicon-law':
     'prefix': 'octicon-law'
-    'body': '<${1:span} class="octicon octicon-law">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-law$2">$3</${1:span}>'
   'octicon-light-bulb':
     'prefix': 'octicon-light-bulb'
-    'body': '<${1:span} class="octicon octicon-light-bulb">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-light-bulb$2">$3</${1:span}>'
   'octicon-link-external':
     'prefix': 'octicon-link-external'
-    'body': '<${1:span} class="octicon octicon-link-external">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-link-external$2">$3</${1:span}>'
   'octicon-link':
     'prefix': 'octicon-link'
-    'body': '<${1:span} class="octicon octicon-link">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-link$2">$3</${1:span}>'
   'octicon-list-ordered':
     'prefix': 'octicon-list-ordered'
-    'body': '<${1:span} class="octicon octicon-list-ordered">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-list-ordered$2">$3</${1:span}>'
   'octicon-list-unordered':
     'prefix': 'octicon-list-unordered'
-    'body': '<${1:span} class="octicon octicon-list-unordered">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-list-unordered$2">$3</${1:span}>'
   'octicon-location':
     'prefix': 'octicon-location'
-    'body': '<${1:span} class="octicon octicon-location">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-location$2">$3</${1:span}>'
   'octicon-lock':
     'prefix': 'octicon-lock'
-    'body': '<${1:span} class="octicon octicon-lock">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-lock$2">$3</${1:span}>'
   'octicon-log-in':
     'prefix': 'octicon-log-in'
-    'body': '<${1:span} class="octicon octicon-log-in">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-log-in$2">$3</${1:span}>'
   'octicon-log-out':
     'prefix': 'octicon-log-out'
-    'body': '<${1:span} class="octicon octicon-log-out">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-log-out$2">$3</${1:span}>'
   'octicon-logo-github':
     'prefix': 'octicon-logo-github'
-    'body': '<${1:span} class="octicon octicon-logo-github">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-logo-github$2">$3</${1:span}>'
   'octicon-mail-read':
     'prefix': 'octicon-mail-read'
-    'body': '<${1:span} class="octicon octicon-mail-read">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mail-read$2">$3</${1:span}>'
   'octicon-mail-reply':
     'prefix': 'octicon-mail-reply'
-    'body': '<${1:span} class="octicon octicon-mail-reply">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mail-reply$2">$3</${1:span}>'
   'octicon-mail':
     'prefix': 'octicon-mail'
-    'body': '<${1:span} class="octicon octicon-mail">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mail$2">$3</${1:span}>'
   'octicon-mark-github':
     'prefix': 'octicon-mark-github'
-    'body': '<${1:span} class="octicon octicon-mark-github">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mark-github$2">$3</${1:span}>'
   'octicon-markdown':
     'prefix': 'octicon-markdown'
-    'body': '<${1:span} class="octicon octicon-markdown">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-markdown$2">$3</${1:span}>'
   'octicon-megaphone':
     'prefix': 'octicon-megaphone'
-    'body': '<${1:span} class="octicon octicon-megaphone">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-megaphone$2">$3</${1:span}>'
   'octicon-mention':
     'prefix': 'octicon-mention'
-    'body': '<${1:span} class="octicon octicon-mention">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mention$2">$3</${1:span}>'
   'octicon-microscope':
     'prefix': 'octicon-microscope'
-    'body': '<${1:span} class="octicon octicon-microscope">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-microscope$2">$3</${1:span}>'
   'octicon-milestone':
     'prefix': 'octicon-milestone'
-    'body': '<${1:span} class="octicon octicon-milestone">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-milestone$2">$3</${1:span}>'
   'octicon-mirror-private':
     'prefix': 'octicon-mirror-private'
-    'body': '<${1:span} class="octicon octicon-mirror-private">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mirror-private$2">$3</${1:span}>'
   'octicon-mirror-public':
     'prefix': 'octicon-mirror-public'
-    'body': '<${1:span} class="octicon octicon-mirror-public">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mirror-public$2">$3</${1:span}>'
   'octicon-mirror':
     'prefix': 'octicon-mirror'
-    'body': '<${1:span} class="octicon octicon-mirror">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mirror$2">$3</${1:span}>'
   'octicon-mortar-board':
     'prefix': 'octicon-mortar-board'
-    'body': '<${1:span} class="octicon octicon-mortar-board">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mortar-board$2">$3</${1:span}>'
   'octicon-move-down':
     'prefix': 'octicon-move-down'
-    'body': '<${1:span} class="octicon octicon-move-down">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-move-down$2">$3</${1:span}>'
   'octicon-move-left':
     'prefix': 'octicon-move-left'
-    'body': '<${1:span} class="octicon octicon-move-left">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-move-left$2">$3</${1:span}>'
   'octicon-move-right':
     'prefix': 'octicon-move-right'
-    'body': '<${1:span} class="octicon octicon-move-right">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-move-right$2">$3</${1:span}>'
   'octicon-move-up':
     'prefix': 'octicon-move-up'
-    'body': '<${1:span} class="octicon octicon-move-up">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-move-up$2">$3</${1:span}>'
   'octicon-mute':
     'prefix': 'octicon-mute'
-    'body': '<${1:span} class="octicon octicon-mute">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-mute$2">$3</${1:span}>'
   'octicon-no-newline':
     'prefix': 'octicon-no-newline'
-    'body': '<${1:span} class="octicon octicon-no-newline">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-no-newline$2">$3</${1:span}>'
   'octicon-octoface':
     'prefix': 'octicon-octoface'
-    'body': '<${1:span} class="octicon octicon-octoface">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-octoface$2">$3</${1:span}>'
   'octicon-organization':
     'prefix': 'octicon-organization'
-    'body': '<${1:span} class="octicon octicon-organization">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-organization$2">$3</${1:span}>'
   'octicon-package':
     'prefix': 'octicon-package'
-    'body': '<${1:span} class="octicon octicon-package">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-package$2">$3</${1:span}>'
   'octicon-paintcan':
     'prefix': 'octicon-paintcan'
-    'body': '<${1:span} class="octicon octicon-paintcan">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-paintcan$2">$3</${1:span}>'
   'octicon-pencil':
     'prefix': 'octicon-pencil'
-    'body': '<${1:span} class="octicon octicon-pencil">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-pencil$2">$3</${1:span}>'
   'octicon-person-add':
     'prefix': 'octicon-person-add'
-    'body': '<${1:span} class="octicon octicon-person-add">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-person-add$2">$3</${1:span}>'
   'octicon-person-follow':
     'prefix': 'octicon-person-follow'
-    'body': '<${1:span} class="octicon octicon-person-follow">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-person-follow$2">$3</${1:span}>'
   'octicon-person':
     'prefix': 'octicon-person'
-    'body': '<${1:span} class="octicon octicon-person">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-person$2">$3</${1:span}>'
   'octicon-pin':
     'prefix': 'octicon-pin'
-    'body': '<${1:span} class="octicon octicon-pin">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-pin$2">$3</${1:span}>'
   'octicon-playback-fast-forward':
     'prefix': 'octicon-playback-fast-forward'
-    'body': '<${1:span} class="octicon octicon-playback-fast-forward">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-playback-fast-forward$2">$3</${1:span}>'
   'octicon-playback-pause':
     'prefix': 'octicon-playback-pause'
-    'body': '<${1:span} class="octicon octicon-playback-pause">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-playback-pause$2">$3</${1:span}>'
   'octicon-playback-play':
     'prefix': 'octicon-playback-play'
-    'body': '<${1:span} class="octicon octicon-playback-play">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-playback-play$2">$3</${1:span}>'
   'octicon-playback-rewind':
     'prefix': 'octicon-playback-rewind'
-    'body': '<${1:span} class="octicon octicon-playback-rewind">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-playback-rewind$2">$3</${1:span}>'
   'octicon-plug':
     'prefix': 'octicon-plug'
-    'body': '<${1:span} class="octicon octicon-plug">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-plug$2">$3</${1:span}>'
   'octicon-plus':
     'prefix': 'octicon-plus'
-    'body': '<${1:span} class="octicon octicon-plus">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-plus$2">$3</${1:span}>'
   'octicon-podium':
     'prefix': 'octicon-podium'
-    'body': '<${1:span} class="octicon octicon-podium">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-podium$2">$3</${1:span}>'
   'octicon-primitive-dot':
     'prefix': 'octicon-primitive-dot'
-    'body': '<${1:span} class="octicon octicon-primitive-dot">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-primitive-dot$2">$3</${1:span}>'
   'octicon-primitive-square':
     'prefix': 'octicon-primitive-square'
-    'body': '<${1:span} class="octicon octicon-primitive-square">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-primitive-square$2">$3</${1:span}>'
   'octicon-pulse':
     'prefix': 'octicon-pulse'
-    'body': '<${1:span} class="octicon octicon-pulse">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-pulse$2">$3</${1:span}>'
   'octicon-puzzle':
     'prefix': 'octicon-puzzle'
-    'body': '<${1:span} class="octicon octicon-puzzle">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-puzzle$2">$3</${1:span}>'
   'octicon-question':
     'prefix': 'octicon-question'
-    'body': '<${1:span} class="octicon octicon-question">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-question$2">$3</${1:span}>'
   'octicon-quote':
     'prefix': 'octicon-quote'
-    'body': '<${1:span} class="octicon octicon-quote">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-quote$2">$3</${1:span}>'
   'octicon-radio-tower':
     'prefix': 'octicon-radio-tower'
-    'body': '<${1:span} class="octicon octicon-radio-tower">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-radio-tower$2">$3</${1:span}>'
   'octicon-remove-close':
     'prefix': 'octicon-remove-close'
-    'body': '<${1:span} class="octicon octicon-remove-close">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-remove-close$2">$3</${1:span}>'
   'octicon-repo-clone':
     'prefix': 'octicon-repo-clone'
-    'body': '<${1:span} class="octicon octicon-repo-clone">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-clone$2">$3</${1:span}>'
   'octicon-repo-create':
     'prefix': 'octicon-repo-create'
-    'body': '<${1:span} class="octicon octicon-repo-create">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-create$2">$3</${1:span}>'
   'octicon-repo-delete':
     'prefix': 'octicon-repo-delete'
-    'body': '<${1:span} class="octicon octicon-repo-delete">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-delete$2">$3</${1:span}>'
   'octicon-repo-force-push':
     'prefix': 'octicon-repo-force-push'
-    'body': '<${1:span} class="octicon octicon-repo-force-push">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-force-push$2">$3</${1:span}>'
   'octicon-repo-forked':
     'prefix': 'octicon-repo-forked'
-    'body': '<${1:span} class="octicon octicon-repo-forked">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-forked$2">$3</${1:span}>'
   'octicon-repo-pull':
     'prefix': 'octicon-repo-pull'
-    'body': '<${1:span} class="octicon octicon-repo-pull">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-pull$2">$3</${1:span}>'
   'octicon-repo-push':
     'prefix': 'octicon-repo-push'
-    'body': '<${1:span} class="octicon octicon-repo-push">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-push$2">$3</${1:span}>'
   'octicon-repo-sync':
     'prefix': 'octicon-repo-sync'
-    'body': '<${1:span} class="octicon octicon-repo-sync">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo-sync$2">$3</${1:span}>'
   'octicon-repo':
     'prefix': 'octicon-repo'
-    'body': '<${1:span} class="octicon octicon-repo">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-repo$2">$3</${1:span}>'
   'octicon-rocket':
     'prefix': 'octicon-rocket'
-    'body': '<${1:span} class="octicon octicon-rocket">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-rocket$2">$3</${1:span}>'
   'octicon-rss':
     'prefix': 'octicon-rss'
-    'body': '<${1:span} class="octicon octicon-rss">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-rss$2">$3</${1:span}>'
   'octicon-ruby':
     'prefix': 'octicon-ruby'
-    'body': '<${1:span} class="octicon octicon-ruby">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-ruby$2">$3</${1:span}>'
   'octicon-screen-full':
     'prefix': 'octicon-screen-full'
-    'body': '<${1:span} class="octicon octicon-screen-full">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-screen-full$2">$3</${1:span}>'
   'octicon-screen-normal':
     'prefix': 'octicon-screen-normal'
-    'body': '<${1:span} class="octicon octicon-screen-normal">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-screen-normal$2">$3</${1:span}>'
   'octicon-search-save':
     'prefix': 'octicon-search-save'
-    'body': '<${1:span} class="octicon octicon-search-save">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-search-save$2">$3</${1:span}>'
   'octicon-search':
     'prefix': 'octicon-search'
-    'body': '<${1:span} class="octicon octicon-search">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-search$2">$3</${1:span}>'
   'octicon-server':
     'prefix': 'octicon-server'
-    'body': '<${1:span} class="octicon octicon-server">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-server$2">$3</${1:span}>'
   'octicon-settings':
     'prefix': 'octicon-settings'
-    'body': '<${1:span} class="octicon octicon-settings">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-settings$2">$3</${1:span}>'
   'octicon-sign-in':
     'prefix': 'octicon-sign-in'
-    'body': '<${1:span} class="octicon octicon-sign-in">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-sign-in$2">$3</${1:span}>'
   'octicon-sign-out':
     'prefix': 'octicon-sign-out'
-    'body': '<${1:span} class="octicon octicon-sign-out">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-sign-out$2">$3</${1:span}>'
   'octicon-split':
     'prefix': 'octicon-split'
-    'body': '<${1:span} class="octicon octicon-split">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-split$2">$3</${1:span}>'
   'octicon-squirrel':
     'prefix': 'octicon-squirrel'
-    'body': '<${1:span} class="octicon octicon-squirrel">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-squirrel$2">$3</${1:span}>'
   'octicon-star-add':
     'prefix': 'octicon-star-add'
-    'body': '<${1:span} class="octicon octicon-star-add">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-star-add$2">$3</${1:span}>'
   'octicon-star-delete':
     'prefix': 'octicon-star-delete'
-    'body': '<${1:span} class="octicon octicon-star-delete">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-star-delete$2">$3</${1:span}>'
   'octicon-star':
     'prefix': 'octicon-star'
-    'body': '<${1:span} class="octicon octicon-star">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-star$2">$3</${1:span}>'
   'octicon-steps':
     'prefix': 'octicon-steps'
-    'body': '<${1:span} class="octicon octicon-steps">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-steps$2">$3</${1:span}>'
   'octicon-stop':
     'prefix': 'octicon-stop'
-    'body': '<${1:span} class="octicon octicon-stop">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-stop$2">$3</${1:span}>'
   'octicon-sync':
     'prefix': 'octicon-sync'
-    'body': '<${1:span} class="octicon octicon-sync">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-sync$2">$3</${1:span}>'
   'octicon-tag-add':
     'prefix': 'octicon-tag-add'
-    'body': '<${1:span} class="octicon octicon-tag-add">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-tag-add$2">$3</${1:span}>'
   'octicon-tag-remove':
     'prefix': 'octicon-tag-remove'
-    'body': '<${1:span} class="octicon octicon-tag-remove">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-tag-remove$2">$3</${1:span}>'
   'octicon-tag':
     'prefix': 'octicon-tag'
-    'body': '<${1:span} class="octicon octicon-tag">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-tag$2">$3</${1:span}>'
   'octicon-telescope':
     'prefix': 'octicon-telescope'
-    'body': '<${1:span} class="octicon octicon-telescope">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-telescope$2">$3</${1:span}>'
   'octicon-terminal':
     'prefix': 'octicon-terminal'
-    'body': '<${1:span} class="octicon octicon-terminal">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-terminal$2">$3</${1:span}>'
   'octicon-three-bars':
     'prefix': 'octicon-three-bars'
-    'body': '<${1:span} class="octicon octicon-three-bars">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-three-bars$2">$3</${1:span}>'
+  'octicon-thumbsdown':
+    'prefix': 'octicon-thumbsdown'
+    'body': '<${1:span} class="octicon octicon-thumbsdown$2">$3</${1:span}>'
+  'octicon-thumbsup':
+    'prefix': 'octicon-thumbsup'
+    'body': '<${1:span} class="octicon octicon-thumbsup$2">$3</${1:span}>'
   'octicon-tools':
     'prefix': 'octicon-tools'
-    'body': '<${1:span} class="octicon octicon-tools">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-tools$2">$3</${1:span}>'
   'octicon-trashcan':
     'prefix': 'octicon-trashcan'
-    'body': '<${1:span} class="octicon octicon-trashcan">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-trashcan$2">$3</${1:span}>'
   'octicon-triangle-down':
     'prefix': 'octicon-triangle-down'
-    'body': '<${1:span} class="octicon octicon-triangle-down">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-triangle-down$2">$3</${1:span}>'
   'octicon-triangle-left':
     'prefix': 'octicon-triangle-left'
-    'body': '<${1:span} class="octicon octicon-triangle-left">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-triangle-left$2">$3</${1:span}>'
   'octicon-triangle-right':
     'prefix': 'octicon-triangle-right'
-    'body': '<${1:span} class="octicon octicon-triangle-right">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-triangle-right$2">$3</${1:span}>'
   'octicon-triangle-up':
     'prefix': 'octicon-triangle-up'
-    'body': '<${1:span} class="octicon octicon-triangle-up">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-triangle-up$2">$3</${1:span}>'
   'octicon-unfold':
     'prefix': 'octicon-unfold'
-    'body': '<${1:span} class="octicon octicon-unfold">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-unfold$2">$3</${1:span}>'
   'octicon-unmute':
     'prefix': 'octicon-unmute'
-    'body': '<${1:span} class="octicon octicon-unmute">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-unmute$2">$3</${1:span}>'
   'octicon-versions':
     'prefix': 'octicon-versions'
-    'body': '<${1:span} class="octicon octicon-versions">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-versions$2">$3</${1:span}>'
   'octicon-x':
     'prefix': 'octicon-x'
-    'body': '<${1:span} class="octicon octicon-x">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-x$2">$3</${1:span}>'
   'octicon-zap':
     'prefix': 'octicon-zap'
-    'body': '<${1:span} class="octicon octicon-zap">$2</${1:span}>'
+    'body': '<${1:span} class="octicon octicon-zap$2">$3</${1:span}>'


Added inline events

Added inline events

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -1,9 +1,46 @@
+Observable = require "./observable"
+
 dataName = "__hamlJR_data"

 if window?
   document = window.document
 else
   document = global.document
+
+eventNames = """
+  abort
+  error
+  resize
+  scroll
+  select
+  submit
+  change
+  reset
+  focus
+  blur
+  click
+  dblclick
+  keydown
+  keypress
+  keyup
+  load
+  unload
+  mousedown
+  mousemove
+  mouseout
+  mouseover
+  mouseup
+  drag
+  dragend
+  dragenter
+  dragleave
+  dragover
+  dragstart
+  drop
+""".split("\n")
+
+isEvent = (name) ->
+  eventNames.indexOf(name) != -1

 Runtime = (context) ->
   stack = []
@@ -103,6 +140,7 @@
   observeAttribute = (name, value) ->
     element = top()

+    # Bi-directional value binding
     if (name is "value") and (typeof value is "function")
       element.value = value()

@@ -112,6 +150,12 @@
       if value.observe
         value.observe (newValue) ->
           element.value = newValue
+    # Straight up onclicks, etc.
+    else if name.match /^on/
+      element[name] = value
+    # Handle click=@method
+    else if isEvent(name)
+      element["on#{name}"] = value
     else
       update = (newValue) ->
         element.setAttribute name, newValue
@@ -249,4 +293,5 @@

   return self

-module.exports = Runtime
+global.Runtime = module.exports = Runtime
+global.Observable = Observable


add failing specs for join patterns with Properties

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1640,6 +1640,27 @@
           [as],         (a)   ->  a)
       ['a', 'ab', 'a', 'abc', 'abc', 'ab'])
   describe "does'nt synchronize on properties", ->
+    expectStreamEvents(
+      ->
+        p = repeat(1, ["p"]).take(100).toProperty()
+        s = series(3, ["1", "2", "3"])
+        Bacon.when(
+          [p,s], (p, s) -> p + s)
+      ["p1", "p2", "p3"])
+    expectStreamEvents(
+      ->
+        p = series(3, ["p"]).toProperty()
+        s = series(1, ["1"])
+        Bacon.when(
+          [p,s], (p, s) -> p + s)
+      [])
+    expectStreamEvents(
+      ->
+        p = repeat(1, ["p"]).take(100).toProperty()
+        s = series(3, ["1", "2", "3"]).toProperty()
+        Bacon.when(
+          [p,s], (p, s) -> p + s)
+      [])
     expectStreamEvents(
       ->
         [a,b,c,_] = ['a','b','c','_']


Add test on sending IPs to addressable connections

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -391,6 +391,49 @@
           done()
       sin1.post new noflo.IP 'data', 'first'
       sin2.post new noflo.IP 'data', 'second'
+
+    it 'should be able to send IPs to addressable connections', (done) ->
+      expected = [
+        data: 'first'
+        index: 1
+      ,
+        data: 'second'
+        index: 0
+      ]
+      c = new noflo.Component
+        inPorts:
+          foo:
+            datatype: 'string'
+        outPorts:
+          baz:
+            datatype: 'boolean'
+            addressable: true
+        process: (input, output) ->
+          return unless input.hasData 'foo'
+          packet = input.get 'foo'
+          output.sendDone new noflo.IP 'data', packet.data,
+            index: expected.length - 1
+
+      c.inPorts.foo.attach sin1
+      c.outPorts.baz.attach sout1, 1
+      c.outPorts.baz.attach sout2, 0
+
+      sout1.on 'ip', (ip) ->
+        exp = expected.shift()
+        received =
+          data: ip.data
+          index: 1
+        chai.expect(received).to.eql exp
+        done() unless expected.length
+      sout2.on 'ip', (ip) ->
+        exp = expected.shift()
+        received =
+          data: ip.data
+          index: 0
+        chai.expect(received).to.eql exp
+        done() unless expected.length
+      sin1.post new noflo.IP 'data', 'first'
+      sin1.post new noflo.IP 'data', 'second'

     it 'should not be triggered by non-triggering ports', (done) ->
       triggered = []


RootView.initialize no longer assigns window.rootView or calls open

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -7,15 +7,13 @@
   [editor, buffer, tabs] = []

   beforeEach ->
-    new RootView(require.resolve('fixtures/sample.js'))
+    window.rootView = new RootView
+    rootView.open('sample.js')
     rootView.open('sample.txt')
     rootView.simulateDomAttachment()
     window.loadPackage("tabs")
     editor = rootView.getActiveEditor()
     tabs = rootView.find('.tabs').view()
-
-  afterEach ->
-    rootView.remove()

   describe "@activate", ->
     it "appends a status bear to all existing and new editors", ->
@@ -25,7 +23,7 @@
       expect(rootView.find('.pane').length).toBe 2
       expect(rootView.panes.find('.pane > .tabs').length).toBe 2

-  describe "#initialize()", ->
+  describe ".initialize()", ->
     it "creates a tab for each edit session on the editor to which the tab-strip belongs", ->
       expect(editor.editSessions.length).toBe 2
       expect(tabs.find('.tab').length).toBe 2


Assert test command different on windows

--- spec/test-spec.coffee
+++ spec/test-spec.coffee
@@ -23,11 +23,17 @@
       atomSpawn.callCount is 1

     runs ->
-      expect(atomSpawn.mostRecentCall.args[0]).toEqual 'atom'
-      expect(atomSpawn.mostRecentCall.args[1][0]).toEqual '--dev'
-      expect(atomSpawn.mostRecentCall.args[1][1]).toEqual '--test'
-      expect(atomSpawn.mostRecentCall.args[1][2]).toEqual "--spec-directory=#{specPath}"
-      expect(atomSpawn.mostRecentCall.args[2].streaming).toBeTruthy()
+      if process.platform is 'win32'
+        expect(atomSpawn.mostRecentCall.args[1][2].indexOf('atom')).not.toBe -1
+        expect(atomSpawn.mostRecentCall.args[1][2].indexOf('--dev')).not.toBe -1
+        expect(atomSpawn.mostRecentCall.args[1][2].indexOf('--test')).not.toBe -1
+        expect(atomSpawn.mostRecentCall.args[1][2].indexOf('--spec-directory')).not.toBe -1
+      else
+        expect(atomSpawn.mostRecentCall.args[0]).toEqual 'atom'
+        expect(atomSpawn.mostRecentCall.args[1][0]).toEqual '--dev'
+        expect(atomSpawn.mostRecentCall.args[1][1]).toEqual '--test'
+        expect(atomSpawn.mostRecentCall.args[1][2]).toEqual "--spec-directory=#{specPath}"
+        expect(atomSpawn.mostRecentCall.args[2].streaming).toBeTruthy()

   describe 'returning', ->
     [callback] = []


Fixed wrong identation.

Fixed wrong identation.

--- client.coffee
+++ client.coffee
@@ -187,7 +187,7 @@

     if @current.message is 'ping'
       @ping @current.data
-     return
+      return

     assert.equal @current.message, 'update'


reset the details form to any entered values

--- overrides.coffee
+++ overrides.coffee
@@ -89,6 +89,10 @@
     ms.svg.attr 'height', h
     ms.rescale current_tool.mark.left - MARGIN, current_tool.mark.top - MARGIN, w, h

+  if task.key is 'details'
+    task.reset if current_tool.mark.details?
+      details: current_tool.mark.details
+
   if task.key is 'parts'
     LAST_TASK = rect_index == rectangles.length - 1
     if LAST_TASK
@@ -99,6 +103,6 @@
 classify_page.el.on 'decision-tree:task-confirm', ({originalEvent: {detail}})->
   console.log 'confirm'

-  if detail.details?
+  if detail?.details?
     current_tool.mark.details = detail.details


fix heightmap scaling

fix heightmap scaling

--- src/3d/Terrain.coffee
+++ src/3d/Terrain.coffee
@@ -24,7 +24,7 @@
   applyHeightmap: (imageData) ->
     i = 0
     for vertice in @mesh.geometry.vertices
-      vertice.z = imageData[i] / 10
+      vertice.z = imageData[i]
       i++

   @heightmap: (textureUrl, heightmapUrl, width, height, wSegments, hSegments, scale=1, scene) ->


modify: remove DOM not used

modify: remove DOM not used

--- assets/coffee/note_list.coffee
+++ assets/coffee/note_list.coffee
@@ -20,6 +20,9 @@
     @listenTo @collection, 'reset', @render

   render: () ->
+    # remove ald views
+    @removeItemViews()
+
     # build own DOMs by tmpelate
     template = $('#noteListView-template').html()
     this.$el.html template
@@ -34,8 +37,13 @@

     # take all models in collection and
     # render each view, then insert it into parent DOM tree
-    this.collection.each (note) ->
+    @itemViews = @collection.map (note) ->
       itemView = new App.NoteListItemView
         model: note
       $insertionPoint.append itemView.render().$el
     , this
+
+  removeItemViews: () ->
+    # call remove() to each view
+    _.invoke @itemViews, 'remove'
+


always be yorick

always be yorick

--- src/GameMaster.coffee
+++ src/GameMaster.coffee
@@ -4,7 +4,7 @@
 class GameMaster extends MS.SingletonEntity
   DELAY = 5000
   init:->
-    @possibleLevels = ["Yorick", "Cleaning", "Killing", "Acting"]
+    @possibleLevels = ["Yorick"]
     @currentState = null
     @lastMove = Date.now()
     @level = 'Lobby'


Use registryEntry.optionList.

Use registryEntry.optionList.

This means that we get the new tabs in the same order as they are
specified in the key mapping.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -152,18 +152,18 @@
         [request.url]
       else
         # Otherwise, if we have a registryEntry containing URLs, then use them.
-        urlList = (opt for own opt of request.registryEntry?.options ? {} when Utils.isUrl opt)
+        urlList = (opt for opt in request.registryEntry.optionList when Utils.isUrl opt)
         if 0 < urlList.length
           urlList
         else
           # Otherwise, just create a new tab.
-          url = Settings.get "newTabUrl"
-          if url == "pages/blank.html"
+          newTabUrl = Settings.get "newTabUrl"
+          if newTabUrl == "pages/blank.html"
             # "pages/blank.html" does not work in incognito mode, so fall back to "chrome://newtab" instead.
             [if request.tab.incognito then "chrome://newtab" else chrome.runtime.getURL newTabUrl]
           else
-            [url]
-    urls = request.urls[..]
+            [newTabUrl]
+    urls = request.urls[..].reverse()
     do openNextUrl = (request) ->
       if 0 < urls.length
         TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) ->


Rename is_method_in_prototype() to does_prototype_have_method().

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -75,7 +75,7 @@
     _throw_expects_usage() unless method_name?
     _throw_reserved_word(method_name) if is_reserved_method_name(method_name)
     _throw_pre_existing_property(method_name) if does_object_have_property(object, method_name)
-    _throw_not_an_existing_method(method_name) if not is_mock_object(object) and is_class(object) and not is_method_in_prototype(object, method_name)
+    _throw_not_an_existing_method(method_name) if not is_mock_object(object) and is_class(object) and not does_prototype_have_method(object, method_name)
     _throw_not_an_existing_method(method_name) if not is_mock_object(object) and not is_class(object) and not does_object_have_method(object, method_name)

   _create_expectation = (object, method_name) ->
@@ -290,8 +290,7 @@
 is_class = (object) ->
   typeof object == 'function'

-# TODO: needs a better function name
-is_method_in_prototype = (object, method_name) ->
+does_prototype_have_method = (object, method_name) ->
   object.prototype[ method_name ]?

 does_object_have_method = (object, method_name) ->


line length > 80

line length > 80

--- public/app.coffee
+++ public/app.coffee
@@ -1,6 +1,7 @@
 class Products
   @url: (shopId) ->
-    "https://site-cors-api-proxy.herokuapp.com/https://developer.epages.com/api/shops/#{shopId}/products"
+    "https://site-cors-api-proxy.herokuapp.com/" +
+    "https://developer.epages.com/api/shops/#{shopId}/products"

   @all: (shopId, fn) ->
     reqwest @url(shopId), (response) ->


changed production url

changed production url

--- public/app.coffee
+++ public/app.coffee
@@ -4,7 +4,7 @@

   @rootUrl:
     switch @env
-      when "production" then "https://site-prototype.herokuapp.com"
+      when "production" then "https://site-production.herokuapp.com"
       else                   "http://localhost:4566"

   @apiUrl:


Config requires all non-banged extensions listed in `core.extensions`

--- src/app/config.coffee
+++ src/app/config.coffee
@@ -16,6 +16,7 @@
     @loadUserConfig()
     @assignDefaults()
     @registerNewExtensions()
+    @requireExtensions()
     @requireUserInitScript()

   loadUserConfig: ->
@@ -46,6 +47,10 @@
       fs.list(bundledExtensionsDirPath)
         .concat(fs.list(userExtensionsDirPath)).map (path) -> fs.base(path)
     _.unique(availableExtensions)
+
+  requireExtensions: ->
+    for extensionName in config.core.extensions
+      requireExtension(extensionName) unless extensionName[0] == '!'

   update: (keyPathString, value) ->
     @setValueAtKeyPath(keyPathString.split('.'), value) if keyPathString


add config option for line height

add config option for line height

This renames the original lineHeight variable to rowHeight to avoid
collision

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -16,6 +16,7 @@
   @configDefaults:
     fontFamily: "Inconsolata, Monaco, Courier"
     fontSize: 20
+    lineHeight: 1.5
     showInvisibles: false
     autosave: false
     autoIndent: true
@@ -308,7 +309,7 @@
     @scrollTop(newScrollTop,  adjustVerticalScrollbar: true)

   getPageRows: ->
-    Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight))
+    Math.max(1, Math.ceil(@scrollView[0].clientHeight / @rowHeight))

   setShowInvisibles: (showInvisibles) ->
     return if showInvisibles == @showInvisibles
@@ -342,6 +343,7 @@
     @observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
     @observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize)
     @observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily)
+    @observeConfig 'editor.lineHeight', (lineHeight) => @setLineHeight(lineHeight)

   handleEvents: ->
     @on 'focus', =>
@@ -575,7 +577,7 @@
       @scrollTop() + @scrollView.height()

   scrollToBottom: ->
-    @scrollBottom(@screenLineCount() * @lineHeight)
+    @scrollBottom(@screenLineCount() * @rowHeight)

   scrollToBufferPosition: (bufferPosition, options) ->
     @scrollToPixelPosition(@pixelPositionForBufferPosition(bufferPosition), options)
@@ -597,12 +599,12 @@
       unless scrollTop < pixelPosition.top < scrollBottom
         @scrollTop(pixelPosition.top - (scrollViewHeight / 2))
     else
-      linesInView = @scrollView.height() / @lineHeight
+      linesInView = @scrollView.height() / @rowHeight
       maxScrollMargin = Math.floor((linesInView - 1) / 2)
       scrollMargin = Math.min(@vScrollMargin, maxScrollMargin)
-      margin = scrollMargin * @lineHeight
+      margin = scrollMargin * @rowHeight
       desiredTop = pixelPosition.top - margin
-      desiredBottom = pixelPosition.top + @lineHeight + margin
+      desiredBottom = pixelPosition.top + @rowHeight + margin
       if desiredBottom > scrollBottom
         @scrollTop(desiredBottom - scrollViewHeight)
       else if desiredTop < scrollTop
@@ -696,6 +698,12 @@

   getFontFamily: -> @fontFamily

+  setLineHeight: (@lineHeight) ->
+    if lineHeight?
+      @css('line-height', "#{lineHeight}em")
+      @redraw()
+
+  getLineHeight: -> @lineHeight

   redraw: ->
     return unless @attached
@@ -798,16 +806,16 @@

     lineRect = fragment[0].getBoundingClientRect()
     charRect = fragment.find('span')[0].getBoundingClientRect()
-    @lineHeight = lineRect.height
+    @rowHeight = lineRect.height
     @charWidth = charRect.width
     @charHeight = charRect.height
-    @height(@lineHeight) if @mini
+    @height(@rowHeight) if @mini
     fragment.remove()

   updateLayerDimensions: ->
     @gutter.calculateWidth()

-    height = @lineHeight * @screenLineCount()
+    height = @rowHeight * @screenLineCount()
     unless @layerHeight == height
       @renderedLines.height(height)
       @underlayer.css('min-height', height)
@@ -1015,19 +1023,19 @@
         row++

   updatePaddingOfRenderedLines: ->
-    paddingTop = @firstRenderedScreenRow * @lineHeight
+    paddingTop = @firstRenderedScreenRow * @rowHeight
     @renderedLines.css('padding-top', paddingTop)
     @gutter.lineNumbers.css('padding-top', paddingTop)

-    paddingBottom = (@getLastScreenRow() - @lastRenderedScreenRow) * @lineHeight
+    paddingBottom = (@getLastScreenRow() - @lastRenderedScreenRow) * @rowHeight
     @renderedLines.css('padding-bottom', paddingBottom)
     @gutter.lineNumbers.css('padding-bottom', paddingBottom)

   getFirstVisibleScreenRow: ->
-    Math.floor(@scrollTop() / @lineHeight)
+    Math.floor(@scrollTop() / @rowHeight)

   getLastVisibleScreenRow: ->
-    Math.max(0, Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1)
+    Math.max(0, Math.ceil((@scrollTop() + @scrollView.height()) / @rowHeight) - 1)

   isScreenRowVisible: (row) ->
     @getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow()
@@ -1126,7 +1134,7 @@

   pixelPositionForScreenPosition: (position) ->
     position = Point.fromObject(position)
-    { top: position.row * @lineHeight, left: position.column * @charWidth }
+    { top: position.row * @rowHeight, left: position.column * @charWidth }

   pixelOffsetForScreenPosition: (position) ->
     {top, left} = @pixelPositionForScreenPosition(position)
@@ -1134,7 +1142,7 @@
     {top: top + offset.top, left: left + offset.left}

   screenPositionFromPixelPosition: ({top, left}) ->
-    screenPosition = new Point(Math.floor(top / @lineHeight), Math.floor(left / @charWidth))
+    screenPosition = new Point(Math.floor(top / @rowHeight), Math.floor(left / @charWidth))

   screenPositionFromMouseEvent: (e) ->
     { pageX, pageY } = e


Don't assume there is a rootView in window.reload

This was causing an error when specs where reloaded.

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -81,7 +81,7 @@
       console.error "Failed to load extension named '#{name}'"

   reload: ->
-    if rootView.getModifiedBuffers().length > 0
+    if rootView?.getModifiedBuffers().length > 0
       atom.confirm(
         "There are unsaved buffers, reload anyway?",
         "You will lose all unsaved changes if you reload",


to_json serialization

to_json serialization

--- src/base_model.coffee
+++ src/base_model.coffee
@@ -69,6 +69,24 @@
     return false if @_isValid
     true

+  to_json: (root = false) ->
+    self = @
+    z = {}
+
+    for attr in @attributes
+      do(attr) ->
+        z["#{attr}"] = self.get("#{attr}")
+
+    if root
+      o = {}
+      name = @constructor.name.replace(/([A-Z])/g, '-$1').replace(/^-/,"").toLowerCase()
+      o[name] = z
+      JSON.stringify(o)
+    else
+      JSON.stringify(z)
+
+

+


DevTools: Remove Run as I type button, moved to application menu

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -46,23 +46,6 @@
       firebaseInstance          : "tw-local"
       panels                    : [
         title                   : "Koding DevTools"
-        buttons                 : [
-          {
-            title               : "Run as I type"
-            cssClass            : "solid #{if @liveMode then 'green' else 'live'}"
-            callback            : =>
-
-              button = @workspace.panels.first.headerButtons['Run as I type']
-              button.unsetClass 'live green'
-              button.setClass if @liveMode then 'live' else 'green'
-              @liveMode = button.hasClass 'green'
-              @storage.setValue 'liveMode', @liveMode
-
-              if @liveMode
-                @previewApp yes; @previewCss yes
-
-          }
-        ]
         layout                  :
           direction             : "vertical"
           sizes                 : [ "264px", null ]
@@ -132,8 +115,6 @@

     @workspace.ready =>

-      toggleAutoRun = =>
-        @workspace.panels.first.headerButtons['Run as I type'].click()

       {JSEditor, CSSEditor} = panes = @workspace.activePanel.panesByName

@@ -146,12 +127,8 @@
             _.debounce (@lazyBound 'previewApp', no), 500

           JSEditor.on "RunRequested", @lazyBound 'previewApp', yes
-          JSEditor.on "AutoRunRequested", toggleAutoRun
-
-          JSEditor.on "OpenedAFile", (file, content)->
-            app = KodingAppsController.getAppInfoFromPath file.path
-            button = @workspace.panels.first.headerButtons['Compile']
-            if app then button.enable() else button.disable()
+          JSEditor.on "AutoRunRequested", @bound 'toggleLiveReload'
+
           @on 'closeAllMenuItemClicked', =>
             CSSEditor.closeFile(); JSEditor.closeFile()


builder: rethrow any other errors

builder: rethrow any other errors

--- Builder.coffee
+++ Builder.coffee
@@ -60,6 +60,7 @@
         console.error "You can install graphicsmagick using homebrew:"
         console.error "$ brew install graphicsmagick"
         process.exit(1)
+      throw err

   buildSprites: (options) ->


Fixed some bugs on configure Now throw Errors instead of just strings. Changed TODO from a list to a link to wiki page.

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -17,10 +17,7 @@
 ## - underscore
 ##
 ## @TODO
-## - Implement @default tag
-## - Implement @deprecated tag
-## - Implement @version tag
-## - Implement configuration for different languages
+## [TODO Wiki](https://github.com/adrianlee44/chalkboard/wiki/TODO)
 ##

 ##
@@ -59,11 +56,6 @@
 NEW_LINE        = /\n\r?/
 cwd             = process.cwd()

-for ext, lang of languages
-  regex = "^\\s*#{lang.symbol}{1,2}#{commentRegexStr}"
-  lang.commentRegex = new RegExp regex
-  lang.blockRegex   = new RegExp lang.block
-
 defaults =
   format: "markdown"
   output: null
@@ -73,12 +65,21 @@
   return str unless str
   str[0].toUpperCase() + str[1..]

-_repeatChar = (char, count) ->
+_repeatChar = (char, count = 0) ->
   Array(count+1).join char

 _getLanguages = (source, options = {}) ->
-  ext = path.extname(source) or path.basename(source)
-  return languages[ext]
+  ext  = path.extname(source) or path.basename(source)
+  lang = languages[ext]
+
+  unless lang?
+    throw new Error "Chalkboard does not support type #{ext}"
+
+  regex = "^\\s*#{lang.symbol}{1,2}#{commentRegexStr}"
+  lang.commentRegex = new RegExp regex
+  lang.blockRegex   = new RegExp lang.block
+
+  return lang

 #
 # @function
@@ -370,13 +371,12 @@
 configure = (options) ->
   opts = _.extend {}, defaults, _(options).pick(_(defaults).keys())

-  if program.output and program.join
-    console.error "Cannot use both output and join option at the same time"
-    return process.exit 1
+  if opts.output and opts.join
+    throw new Error "Cannot use both output and join option at the same time"

   # clean the existing file if all comments are compiled to one location
-  if program.join?
-    joinfilePath = path.join cwd, program.join
+  if opts.join?
+    joinfilePath = path.join cwd, opts.join

     # Clear the original file by write empty string into it
     fs.unlinkSync(joinfilePath) if fs.existsSync joinfilePath
@@ -389,9 +389,7 @@
   opts = configure options

   callback = (source, error) ->
-    if error?
-      console.error error
-      process.exit 1
+    throw new Error(error) if error?

     console.log "Generated documentation for #{source}"

@@ -418,7 +416,7 @@
 # @name run
 # @description
 # Start the process of generating documentation with source code
-# @param {Array} List of arguments
+# @param {Array} argv List of arguments
 #
 run = (argv = {})->
   program
@@ -432,6 +430,16 @@
   if program.args.length
     processFiles program
   else
-    console.error program.helpInformation()
-
-Chalkboard = module.exports = {parse, run, read, write, processFiles}
+    console.log program.helpInformation()
+
+Chalkboard = module.exports = {
+  _capitalize,
+  _repeatChar,
+  _getLanguages,
+  parse,
+  run,
+  read,
+  write,
+  processFiles,
+  configure
+}


testability improvements

testability improvements

--- src/coffee/env.coffee
+++ src/coffee/env.coffee
@@ -1,6 +1,8 @@
 # Asserts that environment variable is set and returns its value.
 # When not set, the application will exit
-module.exports.assert = (envName) ->
+module.exports.assert = (envName) -> _assert process, console, envName
+
+module.exports._assert = _assert = (process, console, envName) ->
   unless env = process.env[envName]
     console.error "Error: Environment variable '#{envName}' not set."
     process.exit(1)


Remove config.

Remove config.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -5,8 +5,6 @@
   .argv
 OrderStateSync = require('../main').OrderStateSync
 Rest = require('sphere-node-connect').Rest
-
-Config.showProgress = true

 options =
   config:


chore

chore

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -89,16 +89,6 @@
     @markerColour = null

   joinSession: ->
-    @globalChannel.bind 'puhser:pair-declined', =>
-      decline = new DeclineView
-
-      setTimeout(=>
-        decline.hide('slow');
-      , 3000)
-
-    @globalChannel.bind 'pusher:pair-accepted', =>
-      @pairingSetup()
-
     if @markerColour
       atom.notifications.addError "It looks like you are already in a pairing session. Please open a new window (cmd+shift+N) to start/join a new one."
       return
@@ -111,6 +101,7 @@
       keys = @sessionId.split("-")
       [@app_key, @app_secret] = [keys[0], keys[1]]
       joinView.panel.hide()
+      @connectToPusher()
       @queue.add(@globalChannel.name, 'pusher:user_asking_to_join', {})

   generateSessionId: ->
@@ -128,18 +119,17 @@
       fn(editor)

   pairingSetup: ->
-    @connectToPusher()
     @synchronizeColours()
     @subscriptions.add atom.commands.add 'atom-workspace', 'PairPressure:disconnect': => @disconnect()

   connectToPusher: ->
-    if not @queue
-      @pusher = new Pusher @app_key,
-        authTransport: 'client'
-        clientAuth:
-          key: @app_key
-          secret: @app_secret
-          user_id: @markerColour || "blank"
+
+    @pusher = new Pusher @app_key,
+      authTransport: 'client'
+      clientAuth:
+        key: @app_key
+        secret: @app_secret
+        user_id: @markerColour || "blank"

       @queue = new MessageQueue(@pusher)

@@ -160,6 +150,9 @@
     prompt.on 'click', 'button.yes', =>
       @queue.add @globalChannel.name, 'pusher:pair-accepted'
       prompt.hide 'slow'
+
+    @globalChannel.bind 'pusher:pair-accepted', =>
+      @pairingSetup()

   synchronizeColours: ->
     @globalChannel.bind 'pusher:subscription_succeeded', (members) =>


don't always set ace/mode/coffee

don't always set ace/mode/coffee

--- src/controller.coffee
+++ src/controller.coffee
@@ -2803,7 +2803,7 @@

     @aceEditor.setTheme 'ace/theme/chrome'
     @aceEditor.setFontSize 15
-    @aceEditor.getSession().setMode 'ace/mode/coffee'
+    @aceEditor.getSession().setMode 'ace/mode/' + @options.mode
     @aceEditor.getSession().setTabSize 2

     @aceEditor.on 'change', =>


Fix shift-enter

Fix shift-enter

--- src/controller.coffee
+++ src/controller.coffee
@@ -2351,7 +2351,10 @@

           @newHandwrittenSocket = newSocket

-        else if @textFocus? then @setTextInputFocus null; @redrawMain()
+        else if @textFocus? and not @shiftKeyPressed
+          @setTextInputFocus null; @redrawMain()
+        else
+          return true

       on_keyup: =>
         if @newHandwrittenSocket?


fix parameter order

fix parameter order

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -69,7 +69,7 @@
       for t in $ticket.get()
         _redmine.issues.getById t.id, (data, status, headers, config) ->
           if data.issue?.status.id is TICKET_CLOSED
-            $ticket.remove {id: data.issue.id, url: data.issue.url}
+            $ticket.remove {url: data.issue.url, id: data.issue.id }

   $scope.$on 'notifyAccountChanged', () ->


Let's do some proper recursive conversion

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -31,10 +31,14 @@
     # of time has passed since @expansionState.entries
     # has been converted to a Map
     unless @expansionState.entries instanceof Map
-      temp = new Map()
-      for name, entry of @expansionState.entries
-        temp.set(name, entry)
-      @expansionState.entries = temp
+      convertEntriesToMap = (entries) ->
+        temp = new Map()
+        for name, entry of entries
+          entry.entries = convertEntriesToMap(entry.entries) if entry.entries?
+          temp.set(name, entry)
+        return temp
+
+      @expansionState.entries = convertEntriesToMap(@expansionState.entries)

     @expansionState.entries ?= new Map()
     @status = null


[Completes #26965809] appends the topical and fortune house numbers to each representation marker

--- lib/ephemeris.coffee
+++ lib/ephemeris.coffee
@@ -30,7 +30,7 @@
              , [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 15, 17, 18, 19, 20]
              , [136199, 7066, 50000, 90377, 20000]
              ]
-    "houses": "O"
+    "houses": "W"

   constructor: (@specifics = {}) ->
     @settings = _.allFurther(@defaults, @specifics)
@@ -168,25 +168,55 @@
               what = out[i]['what']
               mark = "#{item.lon}\u00B0"
               mark = ' ' + mark for count in [0..(5 - mark.length)]
+              out[i].marker = true
               out[i].order.push Number(item.lon)
               out[i]['what'] = what.green
               out[i][longitude] = mark.green
           out = _.sortBy out, (obj) -> obj['order'][0]

+          # TODO: an ugly hack - resolve this technical debt!
+          # It all comes from lon 0 being 360.  Perhaps a 2nd
+          # special kind of longitude should be further sub-classed?
+          for idx in [(out.length-1)..0]
+            if out[idx].order[0] is 360 then out[idx].order[0] = 0 else break
+          # Sort again, because of the above...
+          out = _.sortBy out, (obj) -> obj['order'][0]
+
           # Reduce and write to the stream.
           [seq, prev] = [{}, {}]
           for i, item of out
             out[i].extra = false # innocent by default (i.e. not guilty)
-            # TODO: this shoud be checking for hidden longitude degrees, instead
-            if prev[longitude] isnt item[longitude]
+            # Instead of `if prev[longitude] isnt item[longitude]`.
+            # Note: would be nice if we didn't hardcode `order[0]`
+            # to be longitude for sure (technical debt).
+            # It is used all over the place!
+            if prev.order?[0] isnt item.order[0]
+              # console.log item.what + ' ' + item.order[0]
               if _.size(seq) > 1
+                angled = topical = fortune = false
                 # One or more of the angles can be found in this sequence.
                 if (_.union ['AS', 'MC', 'DS', 'IC'], _.keys seq).length >= 1
-                  for id, idx of seq
-                    # Makes any house extra.
-                    out[idx].extra = true if id[0] is 'H'
-              seq = {} # reset the sequence
+                  angled = true
+                for id, idx of seq
+                  # Makes any house extra.
+                  out[idx].extra = true if id[0] is 'H' and angled is true
+                  # Topical & Fortune Houses.
+                  if id[0] is 'T' or id[0] is 'F'
+                    out[idx].extra = true
+                    switch id[0]
+                      when 'T' then topical = id.substr 1
+                      when 'F' then fortune = id.substr 1
+                if topical isnt false
+                  out[i_mark].what += " #{topical}".white
+                  if fortune isnt false
+                    out[i_mark].what += " / #{fortune}".white
+              # Reset the sequence after a longitude change is processed.
+              seq = {}
+            # From here on, there is always at least one one id in the sequence.
             seq["#{item.id}"] = i
+            # It's mportant to set the marker index after processing.
+            # Otherwise, sometimes the next marker steals the topics.
+            i_mark = i if item.marker is true
             prev = item
           outer = _.filter out, (final) -> final.extra is false
           stream.write cliff.stringifyObjectRows outer, titles, color


Remove ctor handled in superclass

Remove ctor handled in superclass

--- lib/file-view.coffee
+++ lib/file-view.coffee
@@ -3,8 +3,6 @@

 module.exports =
 class FileView extends SymbolsView
-  constructor: (@stack) ->
-    super
   initialize: ->
     super


moved things around

moved things around

--- lib/generator.coffee
+++ lib/generator.coffee
@@ -40,8 +40,6 @@
       parentPathArray = pathArray.slice 0, pathArray.length - 1
       @addCoffeePathArrayAndFile parentPathArray, null, peek pathArray

-
-
     @directoriesWithCoffee[path] ||= files:[], subdirs:[]
     @directoriesWithCoffee[path].namespacePath = (upperCamelCase namespace for namespace in pathArray.slice (@rootArray.length - 1)).join '.'
     # pushIfUnique @directoriesWithCoffee[path].files, "index.coffee"
@@ -60,14 +58,6 @@
       @prettyPrint path + "/" + subdir, indent
     for file in pathInfo.files
       log indent + upperCamelCase file.split(/\.coffee$/)[0]
-
-  generateFiles: ->
-    log "generateFiles"
-    for path, pathInfo of @directoriesWithCoffee
-      log "\ngenerate: #{path.yellow}/namespace.coffee"
-      log indent @generateNamespace(path, pathInfo).green
-      log "\ngenerate: #{path.yellow}/index.coffee"
-      log indent @generateIndex(path, pathInfo).green

   getNameSpaceNamesFromPath: (path) ->
     [..., parentNameSpaceName, nameSpaceName] = path.split('/')
@@ -142,6 +132,13 @@
       """
     result

+  generateFiles: ->
+    for path, pathInfo of @directoriesWithCoffee
+      log "\ngenerate: #{path.yellow}/namespace.coffee"
+      log indent @generateNamespace(path, pathInfo).green
+      log "\ngenerate: #{path.yellow}/index.coffee"
+      log indent @generateIndex(path, pathInfo).green
+
   generateFromFiles: (files) =>
     for file in files when !file.match /(namespace|index)\.coffee$/
       @addCoffeeFile file


Update ldfa-main.coffee

Update ldfa-main.coffee

--- lib/ldfa-main.coffee
+++ lib/ldfa-main.coffee
@@ -24,7 +24,21 @@
         # register commands
         @subscriptions.add atom.commands.add 'atom-workspace', 'linked-data-fragments-atom:startServer': => @startServer()
         @subscriptions.add atom.commands.add 'atom-workspace', 'linked-data-fragments-atom:stopServer': => @stopServer()
+###

+  switch (data.cmd) {
+    case 'start':
+      self.postMessage('WORKER STARTED: ' + data.msg);
+      break;
+    case 'stop':
+      self.postMessage('WORKER STOPPED: ' + data.msg + '. (buttons will no longer work)');
+      self.close(); // Terminates the worker.
+      break;
+    default:
+      self.postMessage('Unknown command: ' + data.msg);
+  };
+
+###
     deactivate: ->
         console.log 'LDFA deactivated'
         @subscriptions.dispose()


Fix comment

Fix comment

--- lib/processor.coffee
+++ lib/processor.coffee
@@ -43,7 +43,7 @@

   # Remove unnecessary pefixes
   remove: (css) ->
-    # Keyframes
+    # At-rules
     css.eachAtRule (rule, i) =>
       if @prefixes.remove['@' + rule.name]
         rule.parent.remove(i) if not @disabled(rule)


modified groupchat presence to set the actual botname, not the jid

--- src/hubot/xmpp.coffee
+++ src/hubot/xmpp.coffee
@@ -29,7 +29,7 @@
     # join each room
     # http://xmpp.org/extensions/xep-0045.html for XMPP chat standard
     for room in @options.rooms
-      @client.send(new Xmpp.Element('presence', to: "#{room}/#{@options.username}" )
+      @client.send(new Xmpp.Element('presence', to: "#{room}/#{@name}" )
         .c('x', xmlns: 'http://jabber.org/protocol/muc' )
         .c('history', seconds: 1 )) # prevent the server from confusing us with old messages
                                     # and it seems that servers don't reliably support maxchars


Reject with error message if any

Reject with error message if any

--- lib/test_case.coffee
+++ lib/test_case.coffee
@@ -41,12 +41,19 @@
   start: ->
     w.promise (resolve, reject) ->
       browser = wd.remote()
-      browser.init browserName: Config.browserName, ->
-        browser.get 'http://localhost:' + Config.ports[0] + '/' + token, resolve
+      browser.init browserName: Config.browserName, (err) ->
+        if err
+          reject(err.message)
+        else
+          browser.get 'http://localhost:' + Config.ports[0] + '/' + token, resolve

   stop: ->
     w.promise (resolve, reject) ->
-      browser.quit(resolve)
+      browser.quit (err) ->
+        if err
+          reject(err.message)
+        else
+          resolve()

   on: (event, callback) ->
     (history[event] || []).forEach (args) ->


e -> event

e -> event

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -590,11 +590,11 @@

     entryToSelect = event.currentTarget

-    if e.shiftKey
+    if event.shiftKey
       @selectContinuousEntries(entryToSelect)
       @showMultiSelectMenu()
     # only allow ctrl click for multi selection on non darwin systems
-    else if e.metaKey or (e.ctrlKey && process.platform isnt 'darwin')
+    else if event.metaKey or (event.ctrlKey && process.platform isnt 'darwin')
       @selectMultipleEntries(entryToSelect)

       # only show the multi select menu if more then one file/directory is selected


:fire: checkSelections #13

:fire: checkSelections #13

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -35,7 +35,6 @@
   replaceModeListener: null
   developer: null
   lastOperation: null
-  # lastVisual: null

   # Mode handling is delegated to modeManager
   delegatingMethods = [
@@ -98,7 +97,6 @@
       @activate('normal') # reset to base mdoe.
       @editorElement.component?.setInputEnabled(true)
       @removeClass packageScope, 'normal-mode'
-    # @editorElement.removeEventListener 'mouseup', @checkSelections
     @editor = null
     @editorElement = null
     @lastOperation = null
@@ -301,21 +299,6 @@
   getSearchHistoryItem: (index=0) ->
     @searchHistory.getEntries[index]

-  checkSelections: ->
-    return unless @editor?
-    if @editor.getSelections().every((s) -> s.isEmpty())
-      @activate('normal') if @isMode('visual')
-    else
-      if @isMode('normal')
-        @activate('visual', 'characterwise')
-      else
-        # When cursor is added selection is empty
-        # using editor.onDidAddCursor not work since at the timing event callbacked,
-        # cursor.selection is `undefined` and editor.getCursors().length isnt editor.getSelections().length
-        lastSelection = @editor.getLastSelection()
-        if lastSelection.isEmpty()
-          lastSelection.selectRight()
-
   updateCursorsVisibility: ->
     return unless settings.get('showCursorInVisualMode')
     cursors = switch


Fix __filename on Windows.

Fix __filename on Windows.

--- renderer/lib/init.coffee
+++ renderer/lib/init.coffee
@@ -39,7 +39,11 @@

 # Set the __filename to the path of html file if it's file:// protocol.
 if window.location.protocol is 'file:'
-  global.__filename = window.location.pathname
+  global.__filename =
+    if process.platform is 'win32'
+      window.location.pathname.substr 1
+    else
+      window.location.pathname
   global.__dirname = path.dirname global.__filename

   # Also search for module under the html file.


Increase change limit to 1000

Increase change limit to 1000

--- app/coffee/RangesManager.coffee
+++ app/coffee/RangesManager.coffee
@@ -3,7 +3,7 @@

 module.exports = RangesManager =
 	MAX_COMMENTS: 500
-	MAX_CHANGES: 500
+	MAX_CHANGES: 1000

 	applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
 		{changes, comments} = entries


[server] geo - manual lookup for Congo (DRC)

--- server/geo.coffee
+++ server/geo.coffee
@@ -267,7 +267,8 @@
     "country-code": "178"
   }
   {
-    name: "Congo, the Democratic Republic of the"
+#    name: "Congo, the Democratic Republic of the"
+    name: "Congo (DRC)"
     "alpha-3": "COD"
     "country-code": "180"
   }


Add treatment/encounter dateline TODO.

Add treatment/encounter dateline TODO.

--- app/javascripts/dateline.coffee
+++ app/javascripts/dateline.coffee
@@ -57,6 +57,27 @@
         a = createSessionDetailLink(ticks[i], sessions[i])
         callback(a) if callback

+    # TODO - Add treatments and encounters to the dateline.
+    # Each treatment is a bar spanning the start and end dates.
+    # Each encounter is a flash glyph.
+    # The treatment and encounter types are distinguished by color.
+    # There are legends for each type.
+    # To do so, make a tri-partite config on a heterogenous data spec:
+    #   visitTicks = [{session:sess, tick:tick}, ...]
+    #   trtTicks = [{treatment:trt, start:tick, end:tick}, ...]
+    #   encTicks = [{encounter:enc, tick:tick}, ...]
+    #
+    # addTreatmentBars = (session, chart) ->
+    #   # Select the SVG element.
+    #   svg = d3.select(chart.container)
+    #   # The x axis element.
+    #   xAxis = svg.select('.nv-x')
+    #   # The tick elements.
+    #   ticks = xAxis.selectAll('.tick')[0]
+    #   for i in [0...ticks.length]
+    #     a = createSessionDetailLink(ticks[i], sessions[i])
+    #     callback(a) if callback
+
     # @param data the session array
     # @returns the nvd3 chart configuration
     configureChart: (sessions) ->
@@ -74,5 +95,5 @@
       # * the addSessionDetailLinks function
       _.extend Chart.configureChart(sessions, dataSpec),
         xValues: (dataSpec.x.accessor(sess) for sess in sessions)
-        xFormat: Chart.dateFormat
+        xFormat: Chart.dateFormat
   ]


Update bridge pillars

Update bridge pillars

--- app/scripts/scenery/City.coffee
+++ app/scripts/scenery/City.coffee
@@ -78,11 +78,11 @@
   height = 65
   @add(0, @level.visibleHeight - 10, Crafty.e('2D, Solid').attr(w: @delta.x, h: 10))

-  water1 = Crafty.e('2D, WebGL, waterFront1, Wave1').attr(z: -200).attr(attrs)
+  water1 = Crafty.e('2D, WebGL, waterFront1, Wave1').attr(z: -20).attr(attrs)
   @add(0, @level.visibleHeight - height, water1)
   water1.originalY = water1.y

-  water2 = Crafty.e('2D, WebGL, waterFront2, Wave2').attr(z: -200).attr(attrs)
+  water2 = Crafty.e('2D, WebGL, waterFront2, Wave2').attr(z: -20).attr(attrs)
   @add(400, @level.visibleHeight - height, water2)
   water2.originalX = water2.x
   water2.originalY = water2.y
@@ -485,20 +485,23 @@
     @addBackground(0, 305,  @deck(.45, w: 600, z: -270).flip('X'), .60)
     @addBackground(0, 255,  @deck(.35, w: 650, z: -260), .65)

-    @addBackground(140, 260,  @pillar( .35, h: 220, z: -261), .65)
-    @addBackground(970, 260,  @pillarX(.35, h: 220, z: -261), .65)
+    @addBackground(140, 280,  @pillar( .35, h: 200, z: -261), .65)
+    @addBackground(970, 280,  @pillarX(.35, h: 200, z: -261), .65)

     @addBackground(0, 205,  @deck(.25, w: 700, z: -50).flip('X'), .70)
     @addBackground(0, 155,  @deck(.15, w: 750, z: -40), .75)
+
+    @addBackground(160, 150, @pillar( 0, h: 350, z: -31), .8)
+    @addBackground(990, 150, @pillarX(0, h: 350, z: -31), .8)
     @addBackground(0, 95,  @deck(.05, w: 800, z: -30).flip('X'), .8)
-
-    @addBackground(180, 10,  @pillar(0, h: 500, z: -21), .9)
-    @addBackground(1020, 10,  @pillarX(0, h: 500, z: -21), .9)

     @addBackground(0, 20,   @deck(0,   w: 900, z: -20), .9)

     @addBackground(0, -60,  @deck(0,   w: 1000, z: -10).flip('X'), 1.0)
     @addBackground(0, -180, @deck(0,   w: 1200, z: 100, lightness: 0.6, blur: 6.0), 1.2)
+
+    @addBackground(190,  -80, @pillar( 0, h: 750, z: 80, lightness: 0.6, blur: 6.0), 1.2)
+    @addBackground(1025, -80, @pillarX(0, h: 750, z: 80, lightness: 0.6, blur: 6.0), 1.2)


tasks: don't search on a single letter

tasks: don't search on a single letter

--- salvus/page/tasks.coffee
+++ salvus/page/tasks.coffee
@@ -179,7 +179,7 @@
         search = @selected_hashtags()
         for x in misc.split(@element.find(".salvus-tasks-search").val().toLowerCase())
             x = $.trim(x)
-            if x.length > 0
+            if x.length > 1
                 search.push(x)
         search_describe = @element.find(".salvus-tasks-search-describe")
         search_describe.find("span").hide()


Added defaults snippet

Added defaults snippet

--- snippets/php.cson
+++ snippets/php.cson
@@ -186,6 +186,18 @@
     \t\'${1}\' => \'${2}\',
     );${0}
     """
+  'defaults':
+    'prefix': 'defaults'
+    'body': """
+    /**
+     * Define the default values for all the $db fields
+     * @var array
+     */
+    private static $defaults = array(
+        \t'${1}' => '${2}'
+    );
+    """
+
   'default_sort':
     'prefix': 'default_sort '
     'body': """


fix permission display for permissions that don't apply to role

test plan:
* teacher-only permissions (e.g. "Add/remove students")
should be shown as disabled for student roles

closes #CNVS-36025

Change-Id: Ic775d8bbc64559c7db0a8ef09dd92f24f192928b
Reviewed-on: https://gerrit.instructure.com/107102
Tested-by: Jenkins
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
QA-Review: Heath Hales <8ca81b68c7e2592ac614bd07f22fcc735ca55f93@instructure.com>
Product-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/views/roles/PermissionButtonView.coffee
+++ app/coffeescripts/views/roles/PermissionButtonView.coffee
@@ -283,7 +283,7 @@
     #
     # @api private
     isDefault: ->
-      !@isExplicit() && !@isLocked()
+      !@isExplicit()

     # Method Summary
     #   Read only attribute is set means you cannot change this permission.


2nd try at merge. Still needs work.

2nd try at merge. Still needs work.

--- js/binding/src/binding.coffee
+++ js/binding/src/binding.coffee
@@ -3,6 +3,21 @@
 # JEFRi is freely distributable under the MIT license.
 # For all details and documentation:
 # http://jefri.org
+
+((($) ->
+	$.fn.merge = (doms) ->
+		doms.each (i, dom) =>
+			dom = $(dom)
+			child = this.children("#" + dom.attr("id"))
+			if child.length is 0
+				this.append(dom.remove())
+			else
+				child.merge(dom.children())
+				# dom.children removed, replace kids
+				this.children(":not([id])").remove()
+				this.append(dom.children().remove())
+		this
+).call(this, jQuery))

 (((_, $, JEFRi)->
 	# A deferred to handle Binding's ready state.
@@ -21,7 +36,7 @@

 	# Add a new root block to our templates.
 	mergeTemplate = (html) ->
-		template.append($(html))
+		template.merge($(html))

 	# Load several templates
 	loadTemplates = (templates) ->


handling the exception when the text() or html() call with a non-exist element

--- src/quo.output.coffee
+++ src/quo.output.coffee
@@ -4,12 +4,12 @@
         if value or $$.toType(value) is "number"
           @each -> @textContent = value
         else
-            @[0].textContent
+            (if @length > 0 then @[0].textContent else '')

     $$.fn.html = (value) ->
         type = $$.toType(value)
         if type is "undefined"
-            @[0].innerHTML
+            (if @length > 0 then @[0].innerHTML else)
         else
             @each ->
                 if type is "string"


remove unused deploy.coffee from bin

remove unused deploy.coffee from bin

--- server/bin/deploy.coffee
+++ server/bin/deploy.coffee
@@ -1,13 +0,0 @@
-console.log "Starting deploy"
-CommandRunner = require('./command-runner')
-Deploy = require('../lib/deploy')
-
-tagName = process.argv[2]
-
-Deploy.deploy(tagName).then( ->
-  console.log 'Successfully deployed'
-).catch( (err) ->
-  console.log 'Deploy failed'
-  console.error err
-)
-


Add test for opening case with an urn without an existing case

--- karma/test-controllers.coffee
+++ karma/test-controllers.coffee
@@ -544,7 +544,7 @@
         expect(MessageService.bulkFlag).toHaveBeenCalledWith([test.msg2], true)
       )

-      it('onCaseWithoutMessage', () ->
+      it('onCaseWithoutMessage existing case', () ->
         createCaseModal = spyOnPromise($q, $scope, ModalService, 'create_case')
         openCase = spyOnPromise($q, $scope, CaseService, 'open')
         redirect = spyOnPromise($q, $scope, UtilsService, 'navigate')
@@ -557,6 +557,21 @@

         openCase.resolve({is_new: false, id: 7})
         expect(UtilsService.navigate).toHaveBeenCalledWith('case/read/7/?alert=open_found_existing')
+      )
+
+      it('onCaseWithoutMessage no existing case', () ->
+        createCaseModal = spyOnPromise($q, $scope, ModalService, 'create_case')
+        openCase = spyOnPromise($q, $scope, CaseService, 'open')
+        redirect = spyOnPromise($q, $scope, UtilsService, 'navigate')
+
+        $scope.onCaseWithoutMessage()
+        expect(ModalService.create_case).toHaveBeenCalledWith({title: 'Create case'})
+
+        createCaseModal.resolve({text: 'test summary', partner: 2, user: 3, urn: 'tel:123'})
+        expect(CaseService.open).toHaveBeenCalledWith(null, 'test summary', 2, 3, 'tel:123')
+
+        openCase.resolve({is_new: true, id: 7})
+        expect(UtilsService.navigate).toHaveBeenCalledWith('case/read/7/')
       )
     )


Fix 404 route, move authenticated API onto its own router

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -926,38 +926,41 @@
       resp.status 404
       return resp.render 'not_found'

-app.all '*', ensureAuthenticated
-
-app.get '/logout', logout
+
+api = express.Router()
+api.use ensureAuthenticated
+app.use "/api", api
+

 # API!
-app.get '/api/tools/?', listTools
-app.post '/api/tools/?', googleAnalytics, postTool
-
-app.put '/api/user/?', updateUser
-
-app.get '/api/:user/datasets/?', checkThisIsMyDataHub, listDatasets
-app.get '/api/:user/datasets/:id/?', checkThisIsMyDataHub, getDataset
-app.get '/api/:user/datasets/:id/views/?', checkThisIsMyDataHub, listViews
-app.put '/api/:user/datasets/:id/?', checkThisIsMyDataHub, updateDataset
-app.post '/api/:user/datasets/?', checkThisIsMyDataHub, addDataset
-app.post '/api/:user/datasets/:dataset/views/?', checkThisIsMyDataHub, addView
-
-app.get '/api/user/?', listUsers
-
-app.post '/api/:user/sshkeys/?', addSSHKey
-app.get '/api/:user/sshkeys/?', listSSHKeys
-
-app.put '/api/:user/subscription/change/:plan/?', changePlan
-app.get '/api/:user/subscription/billing', redirectToRecurlyAdmin
-
-app.post '/api/reporting/message/?', sendIntercomMessage
-app.post '/api/reporting/user/?', sendIntercomUserData
-app.post '/api/reporting/tag/?', sendIntercomTag
+api.get '/tools/?', listTools
+api.post '/tools/?', googleAnalytics, postTool
+
+api.put '/user/?', updateUser
+
+api.get '/:user/datasets/?', checkThisIsMyDataHub, listDatasets
+api.get '/:user/datasets/:id/?', checkThisIsMyDataHub, getDataset
+api.get '/:user/datasets/:id/views/?', checkThisIsMyDataHub, listViews
+api.put '/:user/datasets/:id/?', checkThisIsMyDataHub, updateDataset
+api.post '/:user/datasets/?', checkThisIsMyDataHub, addDataset
+api.post '/:user/datasets/:dataset/views/?', checkThisIsMyDataHub, addView
+
+api.get '/user/?', listUsers
+
+api.post '/:user/sshkeys/?', addSSHKey
+api.get '/:user/sshkeys/?', listSSHKeys
+
+api.put '/:user/subscription/change/:plan/?', changePlan
+api.get '/:user/subscription/billing', redirectToRecurlyAdmin
+
+api.post '/reporting/message/?', sendIntercomMessage
+api.post '/reporting/user/?', sendIntercomUserData
+api.post '/reporting/tag/?', sendIntercomTag
+
+app.get '/logout', ensureAuthenticated, logout

 # Magic redirects
-app.all '*', enforceFreeTrial
-app.get /^[/]dataset[/]([a-zA-Z0-9]+)/, switchContextIfRequiredAndAllowed, renderClientApp
+app.get /^[/]dataset[/]([a-zA-Z0-9]+)/, ensureAuthenticated, switchContextIfRequiredAndAllowed, renderClientApp

 # Define the URLs which get rendered client side
 {ScraperwikiViews} = require '../../shared/code/views'
@@ -965,37 +968,22 @@
   if view.name == "fourOhFour"
     # Don't route fourOhFour
     continue
-  app.get RegExp("^/" + view.route), renderClientApp
-
-# "app.router" positions our routes
-# above the middleware defined below,
-# this means that Express will attempt
-# to match & call routes _before_ continuing
-# on, at which point we assume it's a 404 because
-# no route has handled the request.
-app.use(app.router);
-
-# Since this is the last non-error-handling
-# middleware use()d, we assume 404, as nothing else
-# responded.
+  app.get RegExp("^/" + view.route), ensureAuthenticated, renderClientApp

 # $ curl http://localhost:3000/notfound
 # $ curl http://localhost:3000/notfound -H "Accept: application/json"
 # $ curl http://localhost:3000/notfound -H "Accept: text/plain"
 app.use (req, res, next) ->
-  res.status(404);
-
-  # respond with html page
+  res.status 404
+
   if req.accepts('html')
     res.render 'not_found', url: req.url
     return

-  # respond with json
   if req.accepts('json')
     res.send error: 'Not found'
     return

-  # default to plain-text. send()
   res.type('txt').send 'Not found'

 port = process.env.CU_PORT or 3001


Update local proxy target to staging after cookie session fix (#625)

--- server_setup.ozar.coffee
+++ server_setup.ozar.coffee
@@ -330,13 +330,13 @@

   httpProxy = require 'http-proxy'

-  target = process.env.COCO_PROXY_TARGET or 'https://direct.production.ozaria.com'
+  target = process.env.COCO_PROXY_TARGET or 'https://direct.staging.ozaria.com'
   headers = {}

   proxy = httpProxy.createProxyServer({
-    target: target
-    secure: false,
-    headers: headers
+    target,
+    headers,
+    secure: false
   })
   log.info 'Using dev proxy server'
   app.use (req, res, next) ->


Added dataType (json) to Ajax Base class to fix issues with Rails

--- src/spine.ajax.coffee
+++ src/spine.ajax.coffee
@@ -39,6 +39,7 @@
 class Base
   defaults:
     contentType: "application/json"
+    dataType: "json"
     processData: false

   send: (params, defaults) ->


Move Events.add() to server

Move Events.add() to server

--- src/Events.coffee
+++ src/Events.coffee
@@ -17,12 +17,6 @@
     else
       throw new Error('Invalid event argument: ' + arg)

-  add: (arg) ->
-    df = Q.defer()
-    parsed = @parse(arg)
-    collection.insert arg, Promises.toCallback(df)
-    df.promise
-
   getCollection: -> collection

   findByRoles: (roles) ->
@@ -37,9 +31,14 @@
   getUserSelector: (userId) ->
     user = Meteor.users.findOne(_id: userId)
     unless user then throw new Error("Invalid User ID: #{userId}")
-    selector = $or: [{'access.userIds': $in: [userId]}]
+    # Ensure the user IDs and roles match, or there are no access restrictions.
+    selector = $or: [{'access.userIds': $in: [userId]}, {access: $exists: false}]
     unless _.isEmpty(user.roles) then selector.$or.push {'access.roles': $in: user.roles}
     selector
+
+if Meteor.isServer then _.extend Events,
+
+  add: (arg) -> collection.insert(@parse(arg))

 schema = new SimpleSchema
   title:
@@ -77,7 +76,6 @@
   userCollection = UserEvents.getCollection()
   userCollectionId = Collections.getName(userCollection)
   if Meteor.isServer
-    # Meteor.publish 'userEvents', ->
     Meteor.publish 'events', ->
       unless @userId then throw new Meteor.Error(403, 'User must exist for user events publication')

@@ -102,6 +100,8 @@

       eventsCursor.forEach (event) -> addEvents(event._id, event)

+      Logger.info "Published #{eventsCursor.count()} events"
+
       initializing = false
       @ready()
       @onStop ->


Added winston to scrape

Added winston to scrape

--- source/Downloader.coffee
+++ source/Downloader.coffee
@@ -58,3 +58,5 @@
 # that it should be one on 123savemp3 with
 # a query format.
 scrape = (source) ->
+  winston.info('Start scrape for source: ' + source)
+