More pseudocode

More pseudocode

--- views/coffeescript/filters.coffee
+++ views/coffeescript/filters.coffee
@@ -48,17 +48,25 @@
   activatedNodeId = appContext.getActivatedNodeId()
   activatedNode = node for node in nodeData.nodes when node.id == activatedNodeId

-  hideNodeAndSubTree(activatedNode, relsToHide)
+  hideSubGraph(activatedNode, relsToHide, appContext)

-hideNodeAndSubTree = (node, relsToHide) ->
+hideSubGraph = (node, relsToHide, appContext) ->
   # TODO, pseudocode:
-  for rel in getRels(node)
+  nodeData = appContext.getNodeData()
+  for rel in getRels(node, nodeData)
     if relsToHide == "all" || shouldHide(rel, relsToHide)
       hideRel(rel)
-      for node in getRelatedNode(node, rel)
-        relsToHide = extendRelsToHide(getAllRelsFor(node), relsToHide)
-        hideNode(node)
-        hideNodeAndSubTree(node, "all")
+      for relatedNode in getRelatedNode(node, rel, nodeData)
+        hideNode(relatedNode)
+        if (!existsPathBetweenNodesNotContainingRel(relatedNode, node, rel, nodeData)
+          hideSubGraph(relatedNode, "all")
+
+
+existsPathBetweenNodesNotContainingRel = (nodeA, nodeB, rel, nodeData) ->
+
+
+getRels = (node, nodeData) ->
+  (rel for rel in nodeData.rels when rel.data.end_node == node.id || rel.data.start_node == node.id)

 buildCheckboxHtml = (relType, value, enabled) ->


added new Updater class to utils namespace to ease version updates as part of #59

--- src/lib/utils.coffee
+++ src/lib/utils.coffee
@@ -501,10 +501,47 @@
     @started = yes
     @next()

-# Initialize analytics and logging.
+# `Updater` simplifies the process of updating settings between updates.
+# Inlcuding, but not limited to, data transformations and migration.
+class utils.Updater
+
+  # Create a new instance of `Updater` for `namespace`.
+  constructor: (@namespace) ->
+    # Indicate whether or not `namespace` existed initially.
+    @isNew        = not @exists()
+    # Indicate whether or not updates should be performed on new installations.
+    @processIfNew = no
+
+  # Determine whether or not this namespace exists.
+  exists: ->
+    store.get("updates.#{@namespace}")?
+
+  # Remove this namespace.
+  remove: ->
+    store.modify 'updates', (updates) =>
+      delete updates[@namespace]
+
+  # Rename this namespace to `namespace`.
+  rename: (namespace) ->
+    store.modify 'updates', (updates) =>
+      updates[namespace] = updates[@namespace] if updates[@namespace]?
+      @remove()
+      @namespace = namespace
+
+  # Update this namespace to `version` using the `processor` provided when
+  # `version` is newer.
+  update: (version, processor) ->
+    store.modify 'updates', (updates) =>
+      updates[@namespace] ?= ''
+      if updates[@namespace] < version
+        processor?() if not @isNew or @processIfNew
+        updates[@namespace] = version
+
+# Initialize analytics, logging and updates.
 store.init
   analytics: yes
   logger:    {}
+  updates:   {}
 store.modify 'logger', (logger) ->
   logger.assertions ?= no
   logger.enabled    ?= no


added builddefs coffee

added builddefs coffee


reduce error connect

reduce error connect

--- views/akashic-senka-server.cjsx
+++ views/akashic-senka-server.cjsx
@@ -60,19 +60,20 @@
     log response.statusCode
     isSuccess = false
   #serverList 1-990
-  [response, body] = yield request.getAsync "https://www.senka.me/server/#{serverId}/ranking?f=json&lm=990",
-    json: true
-  if response.statusCode == 200
-    senkaList = JSON.stringify(body)
-    try
-      fs.ensureDirSync path.join(APPDATA_PATH, 'akashic-records', "#{memberId}", 'senkaList', "#{serverId}", '990')
-      fs.writeFileSync path.join(APPDATA_PATH, 'akashic-records', "#{memberId}", 'senkaList', "#{serverId}", '990', time), "#{senkaList}", 'utf8'
-    catch e
-      error "Write senkaList file error!#{e}"
-    console.log "save server:#{serverId} senkaList(990) from senkame[#{time}]" if process.env.DEBUG?
-  else
-    log response.statusCode
-    isSuccess = false
+  if isSuccess
+    [response, body] = yield request.getAsync "https://www.senka.me/server/#{serverId}/ranking?f=json&lm=990",
+      json: true
+    if response.statusCode == 200
+      senkaList = JSON.stringify(body)
+      try
+        fs.ensureDirSync path.join(APPDATA_PATH, 'akashic-records', "#{memberId}", 'senkaList', "#{serverId}", '990')
+        fs.writeFileSync path.join(APPDATA_PATH, 'akashic-records', "#{memberId}", 'senkaList', "#{serverId}", '990', time), "#{senkaList}", 'utf8'
+      catch e
+        error "Write senkaList file error!#{e}"
+      console.log "save server:#{serverId} senkaList(990) from senkame[#{time}]" if process.env.DEBUG?
+    else
+      log response.statusCode
+      isSuccess = false
   isDownloading(serverSelectedVersion, isSuccess)
   return


Listen for save events

Listen for save events

--- virtual-file-system/main.coffee
+++ virtual-file-system/main.coffee
@@ -23,7 +23,13 @@
     @shell = new ShellAdapter(this)

     @websocket = new WebSocket("#{serverURI}?token=#{token}")
+    @observeSave()
     @handleEvents()
+
+  observeSave: ->
+    atom.workspace.observeTextEditors (editor) =>
+      editor.getBuffer()?.onWillSave ({path}) =>
+        @save(path)

   handleEvents: ->
     messageCallbacks =
@@ -175,5 +181,10 @@
   trash: (path) ->
     @send {command: 'trash', path}

+  save: (path) ->
+    atom.project.bufferForPath(path).then (textBuffer) =>
+      content = new Buffer(textBuffer.getText()).toString('base64')
+      @send {command: 'save', path, content}
+
 module.exports = new VirtualFileSystem


needs styling

needs styling

--- src/load/list.coffee
+++ src/load/list.coffee
@@ -13,6 +13,6 @@
     console.log "game", key, value
     break unless value
     dict = {game: key, level: 1}
-    rows.push make.anchor " • #{value.name}", dict
+    rows.push make.anchor value.name, dict
   list.add_child make.rows('games', rows)
   root


bugfix

bugfix

--- public/javascripts/people.coffee
+++ public/javascripts/people.coffee
@@ -29,7 +29,7 @@

       image_url = data.profile_image_url.replace '_normal.', '.'
       $form.find('.image_url')
-        .find('img.avatar').attr('src', image_url)
+        .find('img.avatar').attr('src', image_url).end()
         .find('input').val (i, v) -> v or image_url

       $this.next('.spinner').hide()


Scroll to the route list after a search.

--- web/js/views/routes_view.coffee
+++ web/js/views/routes_view.coffee
@@ -22,7 +22,8 @@
         for route in @routes.models
           @onRouteAdded(route)
         @_addMoreBelowButton()
-
+      @_scrollToRoutes()
+
     onRouteAdded: (route) =>
       idx = @routes.indexOf(route)
       routeView = new RouteView(routes: @routes, routeParams: @routeParams, index: idx)
@@ -57,3 +58,6 @@
       @moreBelowButton?.dispose()
       @moreBelowButton = new MoreRoutesButtonView(routes: @routes, loc: 'below')
       @$el.append(@moreBelowButton.render().el)
+
+    _scrollToRoutes: ->
+      setTimeout((=> $('html, body').animate({scrollTop: @$el.offset().top }, 200)), 50)


minor enhances

minor enhances

--- router.coffee
+++ router.coffee
@@ -181,6 +181,7 @@
     res.writeHead(404, {'Content-Type': 'text/html'})
     res.end("""

404 - Resource #{path} not found at this server

+

Node Simple Router v#{dispatch.version}

<button onclick='history.back();'>Back</button>

         """)

@@ -188,6 +189,7 @@
     res.writeHead(405, {'Content-Type': 'text/html'})
     res.end("""

405 - Resource #{path}: #{message}

+

Node Simple Router v#{dispatch.version}

<button onclick='history.back();'>Back</button>

             """)


take only route from db which has a port

--- router.coffee
+++ router.coffee
@@ -78,7 +78,7 @@
             try
                 apps = JSON.parse body
                 for app in apps.rows
-                    @routes["/apps/#{app.slug}"] = app.port
+                    @routes["/apps/#{app.slug}"] = app.port if app.port?
                 callback()
             catch err
                 return callback err


Added math.round utility.

Added math.round utility.

--- src/math/math.coffee
+++ src/math/math.coffee
@@ -18,6 +18,13 @@
   randi: (min, max) -> random.randi(min, max)
   flipCoin: (chance) -> random.flipCoin(chance)

+  # rounds the given number to a certain number of decimals
+  round: (value, decimals=0) ->
+    if decimals == 0
+      Math.round(value)
+    else
+      exp = Math.pow(10, decimals);
+      Math.round(value * exp) / exp;

   ###
     Easing Functions - inspired by http://easings.net


Fix some bad grammar and missing translation

--- app/locale/id.coffee
+++ app/locale/id.coffee
@@ -1,13 +1,13 @@
 module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Indonesian", translation:
   new_home:
-    slogan: "Game untuk belajar pemograman paling menyenangkan."
+    slogan: "Game untuk belajar pemrograman paling menyenangkan."
     classroom_edition: "Edisi Ruang Kelas:"
     learn_to_code: "Belajar membuat kode:"
     play_now: "Mainkan Sekarang"
     im_a_teacher: "Aku seorang guru"
     im_a_student: "Aku seorang murid"
     learn_more: "Pelajari lebih lanjut"
-    classroom_in_a_box: "Sebuah ruangan kelas di-dalam-kotak untuk mengajar ilmu komputer."
+    classroom_in_a_box: "Sebuah ruangan kelas di-dalam-kotak untuk belajar ilmu komputer."
     codecombat_is: "CodeCombat adalah sebuah wadah untuk para murid untuk belajar ilmu komputer sambil bermain permainan yang sesungguhnya."
     our_courses: "Kursus kami telah di uji melalui para pemain secara khusus untuk menjadi lebih baik di ruang kelas, bahkan oleh para guru yang mempunyai sedikit atau tanpa pengalaman pemrograman sebelumnya."
     watch_how: "Saksikan CodeCombat mentransformasi cara orang-orang mempelajari ilmu komputer."
@@ -18,7 +18,7 @@
     getting_students: "Mengajak murid untuk mengetik kode secepat mungkin sangatlah penting untuk mempelajari sintaks pemrograman dan struktur yang tepat."
     educator_resources: "Sumber pendidik"
     course_guides: "dan panduan rangkaian pelajaran"
-    teaching_computer_science: "Mengajari ilmu computer tidak perlu memerlukan gelar yang mahal, karena kami menyediakan peralatan untuk menunjang pendidik dari segala macam latar belakang."
+    teaching_computer_science: "Mengajari ilmu komputer tidak perlu memerlukan gelar yang mahal, karena kami menyediakan peralatan untuk menunjang pendidik dari segala macam latar belakang."
     accessible_to: "Dapat diakses oleh"
     everyone: "semua orang"
     democratizing: "Mendemokrasikan proses belajar membuat kode adalah inti dari filsafat kami. Semua orang mampu untuk belajar membuat kode."
@@ -55,7 +55,7 @@
     courses_available_in: "Kursus tersedia dalam JavaScript dan Python. Kursus Pengembangan Web menggunakan HTML, CSS, dan jQuery"
     boast: "Teka-teki membanggakan yang cukup kompleks untuk memikat para pemain dan pengkode."
     winning: "Kombinasi yang unggul dari permainan RPG dan pemrograman pekerjaan rumah yang berhasil membuat pendidikan ramah anak yang pasti menyenangkan."
-    run_class: "Semua yang Anda butuhkan untuk menjalankan kelas ilmu komputer di sekolah Anda hari ini, latar belakang CS tidak diperlukan."
+    run_class: "Semua yang Anda butuhkan untuk menjalankan kelas ilmu komputer di sekolah Anda hari ini, latar belakang IT tidak diperlukan."
     goto_classes: "Pergi ke Kelasku"
     view_profile: "Lihat Profilku"
     view_progress: "Lihat Kemajuan"
@@ -87,14 +87,14 @@
     create_a_class: "Buat sebuah kelas"
     other: "Lain-lain"
     learn_to_code: "Belajar membuat kode!"
-#    toggle_nav: "Toggle navigation"
+    toggle_nav: "Aktifkan navigasi"
     schools: "Sekolah"
     get_involved: "Ambil Andil"
-#    open_source: "Open source (GitHub)"
+    open_source: "Open source (GitHub)"
     support: "Bantuan"
 #    faqs: "FAQs"
 #    copyright_prefix: "Copyright"
-#    copyright_suffix: "All Rights Reserved."
+    copyright_suffix: "Seluruh Hak Cipta"
     help_pref: "Butuh bantuan? Surel"
     help_suff: "dan kita akan menghubungi!"
 #    resource_hub: "Resource Hub"
@@ -136,12 +136,12 @@
     poll: "Poll" # Tooltip on poll button from /play
     next: "Lanjut" # Go from choose hero to choose inventory before playing a level
     change_hero: "Ganti Jagoan" # Go back from choose inventory to choose hero
-    buy_gems: "Beli Gems"
+    buy_gems: "Beli Permata"
     subscribers_only: "Hanya untuk yang pelanggan!"
     subscribe_unlock: "Berlangganan untuk membuka!"
     subscriber_heroes: "Berlangganan sekarang untuk segera membuka Amara, Hushbaum, and Hattori!"
-    subscriber_gems: "Subscribe today to purchase this hero with gems!"
-    subscriber_gems: "Berlangganan sekarang untuk membeli jagoan ini dengan gems!"
+    subscriber_gems: "Subscribe today to purchase this hero with permata!"
+    subscriber_gems: "Berlangganan sekarang untuk membeli jagoan ini dengan permata!"
     anonymous: "Pemain tak bernama"
     level_difficulty: "Kesulitan: "
     awaiting_levels_adventurer_prefix: "Kami meliris level baru setiap minggunya"
@@ -322,37 +322,37 @@
     books: "Buku-buku"

   common:
-#    back: "Back" # When used as an action verb, like "Navigate backward"
-#    coming_soon: "Coming soon!"
-#    continue: "Continue" # When used as an action verb, like "Continue forward"
-#    next: "Next"
-#    default_code: "Default Code"
+    back: "Kembali" # When used as an action verb, like "Navigate backward"
+    coming_soon: "Segera Hadir!"
+    continue: "Lanjutkan" # When used as an action verb, like "Continue forward"
+    next: "Selanjutnya"
+    default_code: "Kode Asli"
     loading: "Memuat..."
-#    overview: "Overview"
-#    processing: "Processing..."
-#    solution: "Solution"
-#    table_of_contents: "Table of Contents"
+    overview: "Garis Besar"
+    processing: "Memproses..."
+    solution: "Solusi"
+    table_of_contents: "Daftar Isi"
 #    intro: "Intro"
-#    saving: "Saving..."
-#    sending: "Sending..."
-#    send: "Send"
-#    sent: "Sent"
-#    cancel: "Cancel"
-#    save: "Save"
-#    publish: "Publish"
-#    create: "Create"
+    saving: "Menyimpan..."
+    sending: "Mengirim..."
+    send: "Kirim"
+    sent: "Terkirim"
+    cancel: "Batal"
+    save: "Simpan"
+    publish: "Publikasi"
+    create: "Buat"
 #    fork: "Fork"
     play: "Mainkan" # When used as an action verb, like "Play next level"
-#    retry: "Retry"
-#    actions: "Actions"
+    retry: "Coba Lagi"
+    actions: "Aksi-aksi"
     info: "Info"
     help: "Bantuan"
-#    watch: "Watch"
-#    unwatch: "Unwatch"
-#    submit_patch: "Submit Patch"
-#    submit_changes: "Submit Changes"
+    watch: "Tonton"
+    unwatch: "Berhenti Menonton"
+    submit_patch: "Kirim Perbaikan"
+    submit_changes: "Kirim Perubahan"
     save_changes: "Simpan Perubahan"
-#    required_field: "required"
+    required_field: "wajib"

 #  general:
 #    and: "and"
@@ -418,7 +418,7 @@
   units:
     second: "detik"
     seconds: "detik"
-#    sec: "sec"
+    sec: "dtk"
     minute: "menit"
     minutes: "menit"
     hour: "jam"
@@ -484,7 +484,7 @@
     reload_title: "Muat Ulang Semua Kode?"
     reload_really: "Apakah kamu yakin ingin memuat ulang semua level kembali ke awal mula?"
     reload_confirm: "Muat Ulang Semua"
-    test_level: "Level Tes"
+    test_level: "Tes Level"
     victory: "Menang"
     victory_title_prefix: ""
     victory_title_suffix: " Selesai"
@@ -520,7 +520,7 @@
     loading_start: "Memulai Level"
     loading_start_combo: "Memulai Tantangan Combo"
     loading_start_concept: "Memulai Tantangan Konsep"
-    problem_alert_title: "Memperbaiki Kode Kamu"
+    problem_alert_title: "Perbaiki Kode Kamu"
     time_current: "Sekarang:"
     time_total: "Maks:"
     time_goto: "Menuju ke:"
@@ -599,11 +599,12 @@
     tip_programming_not_about_computers: "Ilmu Komputer tidak hanya mengenai komputer sama seperti astronomi tidak hanya seputar teropong. - Edsger Dijkstra"
     tip_mulan: "Percayalah apa yang kamu bisa, maka kamu bisa. - Mulan"
     project_complete: "Proyek Selesai!"
-#    share_this_project: "Share this project with friends or family:"
-#    ready_to_share: "Ready to publish your project?"
-#    click_publish: "Click \"Publish\" to make it appear in the class gallery, then check out what your classmates built! You can come back and continue to work on this project. Any further changes will automatically be saved and shared with your classmates."
-#    already_published_prefix: "Your changes have been published to the class gallery."
-#    already_published_suffix: "Keep experimenting and making this project even better, or see what the rest of your class has built! Your changes will automatically be saved and shared with your classmates."
+    share_this_project: "Share this project with friends or family:"
+    share_this_project: "Share this project with friends or family:"
+    ready_to_share: "Siap untuk mempublikasikan proyekmu?"
+    click_publish: "Klik \"Publikasi\" untuk membuatnya muncul di galeri kelas, lalu cek apakah yang teman sekelasmu buat! Kamu dapat kembali lagi dan melanjutkan proyek ini. Segala perubahan akan secara otomatis disimpan dan dibagikan ke teman sekelas."
+    already_published_prefix: "Perubahan kamu telah dipublikasikan ke galeri kelas"
+    already_published_suffix: "Tetaplah bereksperimen dan membuat proyek ini menjadi lebih baik, atau apa saja yang kelasmu telah buat! Perubahanmu akan secara otomatis disimpan dan dibagikan dengan seluruh teman sekelasmu."
     view_gallery: "Lihat Galeri"
     project_published_noty: "Level kamu telah dipublikasikan!"
     keep_editing: "Tetap Sunting"
@@ -908,14 +909,14 @@
     music_label: "Musik"
     music_description: "Mengubah musik latar menyala/mati."
     editor_config_title: "Konfigurasi Editor"
-#    editor_config_livecompletion_label: "Live Autocompletion"
-#    editor_config_livecompletion_description: "Displays autocomplete suggestions while typing."
+    editor_config_livecompletion_label: "Otomatisasi Komplit"
+    editor_config_livecompletion_description: "Menunjukkan saran otomatis komplit selagi mengetik"
     editor_config_invisibles_label: "Tunjukkan yang Kasat Mata"
     editor_config_invisibles_description: "Menunjukkan yang kasat mata seperti spasi ataupun tabulasi."
     editor_config_indentguides_label: "Tunjukkan Panduan Indentasi"
     editor_config_indentguides_description: "Menunjukkan garis vertikal untuk melihat indentasi lebih baik."
-#    editor_config_behaviors_label: "Smart Behaviors"
-#    editor_config_behaviors_description: "Autocompletes brackets, braces, and quotes."
+    editor_config_behaviors_label: "Bantuan Cerdas"
+    editor_config_behaviors_description: "Otomatis komplit tanda kurung, kurung kurawal, dan tanda petik."

 #  about:
 #    learn_more: "Learn More"


removes redis entry on SIGTERM

removes redis entry on SIGTERM

--- packages/rocketchat-tutum/startup.coffee
+++ packages/rocketchat-tutum/startup.coffee
@@ -13,6 +13,10 @@
 	client.del("frontend:#{process.env.TUTUM_CLIENT_HOST}")
 	client.rpush("frontend:#{process.env.TUTUM_CLIENT_HOST}", process.env.TUTUM_CLIENT_NAME)
 	client.rpush("frontend:#{process.env.TUTUM_CLIENT_HOST}", "http://#{process.env.TUTUM_IP_ADDRESS.split('/')[0]}:3000")
+
+	# removes the redis entry on a SIGTERM
+	process.on 'SIGTERM', ->
+		client.del("frontend:#{process.env.TUTUM_CLIENT_HOST}")

 	day = 86400000


Update ru.coffee

Update ru.coffee

Translate nav module

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -120,21 +120,21 @@
     twitter_follow: "Подписаться"
     teachers: "Учителям"
     careers: "Карьера"
-#    facebook: "Facebook"
-#    twitter: "Twitter"
-#    create_a_class: "Create a Class"
-#    other: "Other"
-#    learn_to_code: "Learn to Code!"
-#    toggle_nav: "Toggle navigation"
-#    jobs: "Jobs"
-#    schools: "Schools"
-#    educator_wiki: "Educator Wiki"
-#    get_involved: "Get Involved"
-#    open_source: "Open source (GitHub)"
-#    support: "Support"
-#    faqs: "FAQs"
-#    help_pref: "Need help? Email"
-#    help_suff: "and we'll get in touch!"
+    facebook: "Facebook"
+    twitter: "Twitter"
+    create_a_class: "Создать класс"
+    other: "Другое"
+    learn_to_code: "Учить код!"
+    toggle_nav: "Переключение навигации"
+    jobs: "Работы"
+    schools: "Школы"
+    educator_wiki: "Словарь педагога"
+    get_involved: "Принять участие"
+    open_source: "Открытый исходный код (GitHub)"
+    support: "Поддержка"
+    faqs: "Часто задаваемые вопросы"
+    help_pref: "Нужна помощь? Email"
+    help_suff: "и мы свяжемся!"

   modal:
     close: "Закрыть"


:art: Remove global shortcut

:art: Remove global shortcut

Moved to init until can plan on how to support it for various
windows/directories

--- atom-main.coffee
+++ atom-main.coffee
@@ -3,8 +3,6 @@
 atoms = require './src/atom/streams.coffee'
 setupPanel = require './src/notational/setup-panel.coffee'
 notationalItems = require './src/atom/notational-items.coffee'
-remote = require 'remote'
-toggleShortcut = 'cmd+alt+space'

 module.exports =
   panel: undefined
@@ -68,22 +66,6 @@
       @panel.show() unless @panel.isVisible()
       @panel.getItem().querySelector('.atom-notational-search').focus()

-    # global shortcut
-    globalShortcut = remote.require('global-shortcut')
-    ret = globalShortcut.register toggleShortcut, ->
-      if atom.getCurrentWindow().isFocused()
-        atom.hide()
-      else
-        atom.show()
-        atom.focus()
-        target = document.body.querySelector('atom-workspace')
-        atom.commands.dispatch(target, 'atom-notational:focus')
-    console.warn "could not reg #{toggleShortcut}" unless ret
-
-    window.addEventListener 'beforeunload', ->
-      if globalShortcut.isRegistered(toggleShortcut)
-        globalShortcut.unregister(toggleShortcut)
-

   disposableAdd: (disposalAction) ->
     new Disposable(disposalAction)


Switch to light themes in Atom

Switch to light themes in Atom

--- atom/config.cson
+++ atom/config.cson
@@ -23,6 +23,10 @@
       "styleguide"
       "update-package-dependencies"
       "welcome"
+    ]
+    themes: [
+      "one-light-ui"
+      "one-light-syntax"
     ]
   editor:
     preferredLineLength: 120


Increase atom term scrollback size

Increase atom term scrollback size

--- atom/config.cson
+++ atom/config.cson
@@ -134,7 +134,7 @@
       optionIsMeta: false
       red: "#cc0000"
       rendererType: "auto"
-      scrollback: 1000
+      scrollback: 500000
       white: "#d3d7cf"
       yellow: "#c4a000"
     "fb-ai-code-search":


Disable all Atom autocomplete packages

Disable all Atom autocomplete packages

--- atom/config.cson
+++ atom/config.cson
@@ -18,6 +18,11 @@
       "bookmarks"
       "autosave"
       "deprecation-cop"
+      "autocomplete-css"
+      "autocomplete-html"
+      "autocomplete-plus"
+      "autocomplete-snippets"
+      "autocomplete-atom-api"
     ]
     projectHome: "~"
     themes: [
@@ -30,7 +35,8 @@
     softTabs: false
     softWrap: true
     softWrapAtPreferredLineLength: true
-    invisibles: {}
+    invisibles:
+      {}
     autoIndentOnPaste: false
   welcome:
     showOnStartup: false
@@ -49,12 +55,14 @@
   "linter-jscs":
     onlyConfig: true
     preset: "jquery"
+  "fuzzy-finder":
+    preserveLastSearch: true
+".applescript.source":
+  editor:
+    commentStart: "-- "
+".ini.source":
+  editor:
+    commentStart: "# "
 ".source.viml":
   editor:
     commentStart: "\" "
-".ini.source":
-  editor:
-    commentStart: "# "
-".applescript.source":
-  editor:
-    commentStart: "-- "


chore(atom/config.cson): update atom config file

Except for switching on triggerOnSave, no notable change is made.

--- atom/config.cson
+++ atom/config.cson
@@ -5,10 +5,13 @@
       enabled: false
     particles: {}
     screenShake: {}
+  "atom-beautify":
+    general: {}
+  "atom-html-preview":
+    triggerOnSave: true
+  "atom-pair": {}
+  "color-picker": {}
   core:
-    disabledPackages: [
-      "markdown-format"
-    ]
     packagesWithKeymapsDisabled: []
     telemetryConsent: "limited"
     themes: [
@@ -21,6 +24,8 @@
     showIndentGuide: true
     softWrap: true
     softWrapAtPreferredLineLength: true
+  emmet: {}
+  "ex-mode": {}
   "exception-reporting":
     userId: "5f1fd872-de33-9d23-b5da-da0fbe791e6c"
   "file-icons": {}
@@ -37,6 +42,8 @@
       "highlight-selected": true
       "highlight-selectedDecorationsZIndex": 0
   "one-dark-ui": {}
+  script: {}
+  "todo-show": {}
   "tree-view": {}
   "vim-mode":
     useSmartcaseForSearch: true


Add key binding for chmod package

Add key binding for chmod package

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -7,3 +7,5 @@
 '.platform-darwin atom-workspace':
   'ctrl-"': 'toggle-quotes:toggle'
   'alt-cmd-n': 'advanced-open-file:toggle'
+'.tree-view':
+  'shift-m': 'chmod:selected-entry'


Add key binding for copying file path in Atom

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -98,6 +98,9 @@
   # Swaps selections
   'alt-cmd-t': 'atom-transpose:transpose'

+  # Copy the current file's project-relative path to the clipboard
+  'alt-cmd-c': 'editor:copy-project-path'
+
   # Reformat a file according to the applicable editorconfig settings
   'alt-cmd-e alt-cmd-f': 'EditorConfig:fix-file'


add missing python keywords

add missing python keywords

--- client/specs/python.coffee
+++ client/specs/python.coffee
@@ -15,7 +15,7 @@
     PAIRS2: "[]"
     PAIRS3: "{}"
     KEY1: "break continue elif else for if pass return while and not or in del".split(" ")
-    KEY2: "class def import from lambda except finally raise try yield".split(" ")
+    KEY2: "class def import as from lambda except finally raise try yield".split(" ")
     KEY3: "abs divmod input open staticmethod
         all enumerate int ord str
         any eval isinstance pow sum


Use createRef and fix variable name

Use createRef and fix variable name

--- resources/assets/coffee/react/profile-page/achievement-badge.coffee
+++ resources/assets/coffee/react/profile-page/achievement-badge.coffee
@@ -24,6 +24,11 @@
     modifiers: []

+  constructor: (props) ->
+    super props
+
+    @tooltip = React.createRef()
+
   render: =>
     @tooltipId = "#{@props.achievement.slug}-#{Math.floor(Math.random() * 1000000)}"

@@ -46,7 +51,7 @@
         className: 'hidden'
         div
           className: 'js-tooltip-achievement--content tooltip-achievement__main'
-          ref: 'tooltip'
+          ref: @tooltip
           div
             className: 'tooltip-achievement__badge'
             div
@@ -103,11 +108,11 @@

     return if elem._loadedTooltipId == @tooltipId

-    content = $(@refs.tooltip).clone()
+    $content = $(@tooltip.current).clone()

     if elem._loadedTooltipId?
       elem._loadedTooltipId = @tooltipId
-      $(elem).qtip 'set', 'content.text': content
+      $(elem).qtip 'set', 'content.text': $content
       return

     elem._loadedTooltipId = @tooltipId
@@ -116,7 +121,7 @@

     options =
       overwrite: false
-      content: content
+      content: $content
       position:
         my: 'bottom center'
         at: 'top center'


Map Input: Fixed a bug where wasn't being possible to change the icon and color in some cases. See #43158

--- src/elements/Map/MapInput.coffee
+++ src/elements/Map/MapInput.coffee
@@ -81,13 +81,16 @@
 			return ""
 		return @__getFormattedPosition(position)

-	getValueForStore: (value) ->
+	getValueForStore: (stringCoordinates) ->
 		objectValue =
 			iconName: @__selectedMarkerOptions.iconName
 			iconColor: @__selectedMarkerOptions.iconColor

-		parsedPosition = CUI.util.parseCoordinates(value)
-		objectValue.position = if parsedPosition then parsedPosition else ""
+		if stringCoordinates
+			parsedPosition = CUI.util.parseCoordinates(stringCoordinates)
+			objectValue.position = if parsedPosition then parsedPosition else ""
+		else
+			objectValue.position = @__getPosition()
 		return objectValue

 	render: ->
@@ -157,6 +160,7 @@
 			form:
 				label: CUI.MapInput.defaults.labels.colorLabel
 			onDataChanged: =>
+				@storeValue()
 				@__updateIconOptions()
 			options: =>
 				options = []
@@ -173,6 +177,7 @@
 			form:
 				label: CUI.MapInput.defaults.labels.iconLabel
 			onDataChanged: =>
+				@storeValue()
 				@__updateIconOptions()
 			options: =>
 				options = []


Request: implement request handler

Request: implement request handler

--- components/Request.coffee
+++ components/Request.coffee
@@ -12,7 +12,11 @@
       request: new noflo.Port 'object'

     @requestHandler = (req) =>
-      @handleRequest(req)
+      unless @outPorts.request.isAttached()
+        req.abort()
+        return
+      @outPorts.request.send(req)
+      @outPorts.disconnect()

     @verb = 'get'
     @path = '/'


Fix feed rendering if no document summary supplied

--- static/js/feed.coffee
+++ static/js/feed.coffee
@@ -42,7 +42,8 @@
             obj.action = act.action
         obj.icon = act.icon
         obj.time_str = make_time_string obj.time
-        obj.target.text = @process_summary obj.target.text
+        if obj.target.text
+            obj.target.text = @process_summary obj.target.text
         html = @template obj
         @$el.html html
         @$el.find('.summary').expander


fixed tags to allow string content

fixed tags to allow string content

--- utils.coffee
+++ utils.coffee
@@ -48,7 +48,7 @@
 		(args...) ->
 			attr = {}
 			last = args[args.length - 1]
-			if last instanceof Element or _.isArray last
+			if last instanceof Node or typeof last is 'string' or _.isArray last
 				content = args.splice(args.length - 1, 1)?[0]
 			for arg in args
 				for key, val of arg
@@ -58,7 +58,9 @@
 				el.setAttribute a, val
 			if not _.isArray content
 				content = [content]
-			content.map (c) -> el.appendChild c if c instanceof Element
+			content.map (c) ->
+				el.appendChild c if c instanceof Element
+				el.appendChild new Text c if typeof c is 'string'
 			el

 	cssClass: (s) -> s?.toLowerCase?()?.replace? /\s|\./g, '-'


Fix override  method issue.

Fix override  method issue.

--- src/coffee/kopi/db/roles.coffee
+++ src/coffee/kopi/db/roles.coffee
@@ -15,13 +15,16 @@
           this.user.name == "admin"

       user = new User()
-      admin = Admin.apply(user)
+      admin = Admin.proxy(user)
     ###
     class Role

       this.model = (model) -> this._modelClass = model

-      this.apply = (model) -> new this(model)
+      this.proxy = (model) ->
+        cls = this
+        role = new cls(model)
+        role

       constructor: (model) -> this.model = model


Make the user id portion of the path optional.

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -237,7 +237,7 @@
 			return null

 		normalizeFilePath = (path) ->
-			path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}-[0-9a-f]{24}\/(\.\/)?/, "")
+			path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}(-[0-9a-f]{24})?\/(\.\/)?/, "")
 			path = path.replace(/^\/compile\//, "")

 			rootDocDirname = ide.fileTreeManager.getRootDocDirname()


Regex escape / for timezones and escape timezone string before sending

--- src/worldcup.coffee
+++ src/worldcup.coffee
@@ -12,9 +12,9 @@
 #   hubot wc <red or yellow> <name> - Give someone a red/yellow card

 module.exports = (robot) ->
-  robot.respond /(worldcup|wc)( today)( [\w \(\&\)]+)?/i, (msg) ->
+  robot.respond /(worldcup|wc)( today)( [\w \(\&\)\/]+)?/i, (msg) ->
     timezone = if msg.match[3]
-      msg.match[3].trim()
+      escape msg.match[3].trim()
     else
       ""

@@ -33,9 +33,9 @@
         else
           msg.send "There are no matches today"

-  robot.respond /(worldcup|wc)( tomorrow)( [\w \(\&\)]+)?/i, (msg) ->
+  robot.respond /(worldcup|wc)( tomorrow)( [\w \(\&\)\/]+)?/i, (msg) ->
     timezone = if msg.match[3]
-      msg.match[3].trim()
+      escape msg.match[3].trim()
     else
       ""

@@ -90,9 +90,9 @@
       msg.send "http://img.thesun.co.uk/aidemitlum/archive/01689/red_main_1689473a.jpg"
       msg.send "#{person}: you're out"

-  robot.respond /(worldcup|wc)( recap)( [\w \(\&\)]+)?/i, (msg) ->
+  robot.respond /(worldcup|wc)( recap)( [\w \(\&\)\/]+)?/i, (msg) ->
     timezone = if msg.match[3]
-      msg.match[3].trim()
+      escape msg.match[3].trim()
     else
       ""

@@ -109,9 +109,9 @@
         else
           msg.send "There were no matches yesterday :("

-  robot.respond /(worldcup|wc)( score)( [\w \(\&\)]+)?/i, (msg) ->
+  robot.respond /(worldcup|wc)( score)( [\w \(\&\)\/]+)?/i, (msg) ->
     timezone = if msg.match[3]
-      msg.match[3].trim()
+      escape msg.match[3].trim()
     else
       ""


Only use uri components for squad name/notes upon page load

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -103,17 +103,24 @@
         @setupUI()
         @setupEventHandlers()

-        @resetCurrentSquad()
-
         if $.getParameterByName('f') == @faction
+            @resetCurrentSquad(true)
             @loadFromSerialized $.getParameterByName('d')
         else
+            @resetCurrentSquad()
             @addShip()
-
-    resetCurrentSquad: ->
+        console.log("done loading page")
+
+    resetCurrentSquad: (initial_load=false) ->
         default_squad_name = 'Unnamed Squadron'
-        squad_name = $.trim(@squad_name_input.val()) or $.trim $.getParameterByName('sn') or default_squad_name
-        squad_notes = $.trim(@notes.val()) or $.trim $.getParameterByName('sno') or ''
+
+        squad_name = $.trim(@squad_name_input.val()) or default_squad_name
+        squad_notes = $.trim(@notes.val()) or ''
+        if initial_load and $.trim $.getParameterByName('sn')
+            squad_name = $.trim $.getParameterByName('sn')
+        if initial_load and $.trim $.getParameterByName('sno')
+            squad_notes = $.trim $.getParameterByName('sno')
+
         @current_squad =
             id: null
             name: squad_name
@@ -129,6 +136,7 @@
                 @current_squad.name = 'Unsaved Squadron'
             @current_squad.dirty = true
         @notes.val(squad_notes)
+        @updatePermaLink()
         @container.trigger 'xwing-backend:squadNameChanged'
         @container.trigger 'xwing-backend:squadDirtinessChanged'


fix(keybindings): Fix "Show Keybindings" button

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -23,6 +23,9 @@
     @_keystrokes = {}
     @_keymapDisposables = {}

+  getUserKeymapPath: ->
+    path.join(@configDirPath, 'keymap.json')
+
   loadBundledKeymaps: ->
     # Load the base keymap and the base.platform keymap
     baseKeymap = fs.resolve(path.join(@resourcePath, 'keymaps', 'base.json'))
@@ -47,7 +50,7 @@
     reloadTemplateKeymap()

   loadUserKeymap: ->
-    userKeymapPath = path.join(@configDirPath, 'keymap.json')
+    userKeymapPath = @getUserKeymapPath()
     return unless fs.isFileSync(userKeymapPath)

     try


Ok ok, we don’t need these

Ok ok, we don’t need these

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -640,11 +640,11 @@
     @cancelPendingState()
     @clearQueuedKeystrokes()

-    binding.enabled = false for binding in bindingsToDisable if bindingsToDisable?
+    binding.enabled = false for binding in bindingsToDisable
     for event in eventsToReplay
       @handleKeyboardEvent(event, true)
       if bindingsToDisable? and not @pendingPartialMatches?
-        binding.enabled = true for binding in bindingsToDisable if bindingsToDisable?
+        binding.enabled = true for binding in bindingsToDisable
         bindingsToDisable = null

     atom?.assert(not bindingsToDisable?, "Invalid keymap state")


Don't log out the settings

Don't log out the settings

--- libraries/settings/Settings.coffee
+++ libraries/settings/Settings.coffee
@@ -39,4 +39,3 @@

 module.exports = defaults

-console.log "Settings", module.exports


Update normalize.css to 4.1.1

Update normalize.css to 4.1.1


Rename private members

Rename private members

--- src/coffee/TimeClockWallPattern.coffee
+++ src/coffee/TimeClockWallPattern.coffee
@@ -18,7 +18,7 @@
   # Gets the pattern
   getHandPositions: ->
     # Get clock patterns for time digits
-    timeNumberPatterns = getTimeNumberPatterns(@hours, @minutes)
+    timeNumberPatterns = _getTimeNumberPatterns(@hours, @minutes)

     # Reset the hand positions
     @resetHandPositions()
@@ -36,18 +36,18 @@
   # ## Private Methods

   # Gets the 3x7 pattern for the number
-  getNumber = (number) ->
+  _getNumber = (number) ->
     if 0 <= number <= 9
       return NumberClockWallPattern[number]
     else
       return null

   # Gets the clock patterns for each digit of the time (hours and minutes)
-  getTimeNumberPatterns = (hours, minutes) ->
-    hourTens = getNumber(~~(hours / 10))
-    hourOnes = getNumber(hours % 10)
-    minuteTens = getNumber(~~(minutes / 10))
-    minuteOnes = getNumber(minutes % 10)
+  _getTimeNumberPatterns = (hours, minutes) ->
+    hourTens = _getNumber(~~(hours / 10))
+    hourOnes = _getNumber(hours % 10)
+    minuteTens = _getNumber(~~(minutes / 10))
+    minuteOnes = _getNumber(minutes % 10)

     # Return all four digits
     return [hourTens, hourOnes, minuteTens, minuteOnes]


Use ctrl-i as auto-indent

Use ctrl-i as auto-indent

--- keymap.cson
+++ keymap.cson
@@ -65,10 +65,9 @@
   'ctrl-o "': 'expand-region:select-around-double-quotes'
   'ctrl-o `': 'expand-region:select-around-back-ticks'
   'ctrl-o t': 'expand-region:select-around-tags'
-
+
   # auto-indent
-  'ctrl-=': 'editor:auto-indent'
-  'shift-ctrl-i': 'editor:auto-indent'
+  'ctrl-i': 'editor:auto-indent'

   # block-travel


:guardsman: Catch error in test of plugin removal

--- lib/minimap-quick-settings-view.coffee
+++ lib/minimap-quick-settings-view.coffee
@@ -98,7 +98,7 @@
       @selectedItem.addClass('selected')

   removeItemFor: (name, plugin) ->
-    @list.remove(@plugins[name])
+    try @list.remove(@plugins[name])
     delete @plugins[name]

   activateItem: (name, plugin) ->


Fix some unit test

Fix some unit test

--- spec/linter-mypy_setting-spec.coffee
+++ spec/linter-mypy_setting-spec.coffee
@@ -739,20 +739,20 @@
         it 'Adds the expected arguments', ->
           expect(messages.length).toBe(1)
           expect(messages[0].excerpt).toContain(" --warn-unreachable ")
+          expect(messages[0].excerpt).not.toContain(" --no--warn-unreachable ")
+          atom.config.set(settingName, oldValue)
+      describe 'Set to False', ->
+        oldValue = atom.config.get(settingName)
+        messages = null
+        beforeEach ->
+          atom.config.set(settingName, false)
+          waitsForPromise ->
+            lint(editor).then (msgs) ->
+              messages = msgs
+        it 'Adds the expected arguments', ->
+          expect(messages.length).toBe(1)
           expect(messages[0].excerpt).not.toContain(" --warn-unreachable ")
-          atom.config.set(settingName, oldValue)
-      describe 'Set to False', ->
-        oldValue = atom.config.get(settingName)
-        messages = null
-        beforeEach ->
-          atom.config.set(settingName, false)
-          waitsForPromise ->
-            lint(editor).then (msgs) ->
-              messages = msgs
-        it 'Adds the expected arguments', ->
-          expect(messages.length).toBe(1)
-          expect(messages[0].excerpt).not.toContain(" --no-warn-unreachable ")
-          expect(messages[0].excerpt).toContain(" --warn-unreachable ")
+          expect(messages[0].excerpt).toContain(" --no--warn-unreachable ")
           atom.config.set(settingName, oldValue)

     describe "With the setting: noImplicitReexport", ->
@@ -768,7 +768,7 @@
         it 'Adds the expected arguments', ->
           expect(messages.length).toBe(1)
           expect(messages[0].excerpt).toContain(" --no-implicit-reexport ")
-          expect(messages[0].excerpt).not.toContain(" --no-implicit-reexport ")
+          expect(messages[0].excerpt).not.toContain(" --implicit-reexport ")
           atom.config.set(settingName, oldValue)
       describe 'Set to False', ->
         oldValue = atom.config.get(settingName)


hotfix: added /ping route

hotfix: added /ping route

--- server.coffee
+++ server.coffee
@@ -20,6 +20,9 @@
     res.json
       FlakCannon: flakCannon.isFulfilled()
       healthy: flakCannon.isFulfilled()
+
+app.use '/ping', (req, res) ->
+  res.end 'pong'

 if process.env.NODE_ENV is 'production'
   app.use express['static'](__dirname + '/dist')


Eventlist fix

Eventlist fix

--- server.coffee
+++ server.coffee
@@ -49,7 +49,10 @@
 		newVersion = 5
 		if Db.shared.peek('gameState') is 1
 			updateTeamRankings()
-
+	if version < 6
+		newVersion = 6
+		if not(Db.shared.peek('game', 'eventlist', 'maxId')?)
+			Db.shared.set 'game', 'eventlist', 'maxId', 0

 	# Write new version to the database
 	if newVersion isnt version
@@ -671,7 +674,7 @@
 	maxId = Db.shared.peek 'game', 'eventlist', 'maxId'
 	log "[addEvent()] Event: " + eventArgs.type + " id: " + maxId
 	Db.shared.set 'game', 'eventlist', maxId, eventArgs
-	Db.shared.set 'game', 'eventlist', 'maxId', (maxId + 1)
+	Db.shared.modify 'game', 'eventlist', 'maxId', (v) -> v + 1

 # Sends a push notification, message, to all team members
 pushToTeam = (teamId, message) ->


Fix system danmaku sender address

Fix system danmaku sender address

--- server.coffee
+++ server.coffee
@@ -631,7 +631,8 @@
     attributes.color ||= '255,0,0,0.9'
     attributes.size ||= 0.75
     attributes.speed ||= 0.75
-    self.say text, '127.0.0.1', attributes
+    attributes.address ||= '127.0.0.1'
+    self.say text, attributes.address, attributes

   ban: (uid, by_, reason, duration = 3600, message = '') =>
     self = this
@@ -757,6 +758,7 @@
           item = items[0]
           target_uid = item.uid
           target_message = item.text
+          throw 'Target UID Not Found' unless self.fragtable[target_uid]
           if self.fragtable[target_uid].flag[uid]
             nodeirc.action("\u000307#{target_uid} has been reported by #{uid})")
           else
@@ -764,7 +766,11 @@
             if object_length(self.fragtable[target_uid].flag) >= 2
               self.ban(target_uid, uid, 'Online Report', 86400, target_message)
             else
-              self.system_say("“#{target_message}” is reported by #{uid}", color: '255,128,0,0.9')
+              self.system_say(
+                "“#{target_message}” is reported by #{uid}",
+                color: '255,128,0,0.9',
+                address: ip
+              )
         catch error
           switch error
             when 'BLACKLIST'


pdf content neg

pdf content neg

--- server.coffee
+++ server.coffee
@@ -53,13 +53,22 @@

 _content_type_from_headers = (headers) ->
     acc = headers.accept
+    if acc.indexOf(',')  > -1
+        acc = acc.substring 0, acc.indexOf(',')
     ct_default = "application/json"
     if acc is null
         return ct_default
     if acc is "*/*"
         return "text/html"
     else
-        return acc.substring(0, acc.indexOf(','))
+        return acc
+
+_findPdfLink = (doc) ->
+    if doc.zotero
+        for att in doc.zotero[0].attachments
+            if att.mimeType is 'application/pdf'
+                return att.url
+    return "---"

 handle_ids_for_id = (req, res) ->
     url_parts = url.parse(req.url, true)
@@ -113,6 +122,12 @@
         #     res.writeHead 200, "Content-Type": "text/html"
         #     res.end jadeDoiInfo({data: results})
         # else
+        console.log _content_type_from_headers(req.headers)
+        if _content_type_from_headers(req.headers) is "application/pdf"
+            res.setHeader "Location", _findPdfLink(results)
+            res.writeHead 303, "Content-Type": "text/html"
+            res.end()
+        else
             res.writeHead 200, "Content-Type": "application/json"
             res.end JSON.stringify(results)


Respond to some API changes

Respond to some API changes

--- server.coffee
+++ server.coffee
@@ -225,20 +225,20 @@
     return if Db.shared.get('rounds', roundId, 'results') or self not in [1..5]
         # round no longer active

-    Db.personal().set 'rankings', roundId, 'self', self
+    Db.personal(Plugin.userId()).set 'rankings', roundId, 'self', self

 exports.client_rankTop = (roundId, values) !->
     return if Db.shared.get('rounds', roundId, 'results') or !values[1] or !values[2] or (topCnt isnt 2 and !values[3])
         # round no longer active

     if values is 'remove'
-        Db.personal().remove 'rankings', roundId
+        Db.personal(Plugin.userId()).remove 'rankings', roundId
     else
         resObj =
             1: +values[1]
             2: +values[2]
             3: +values[3]
-        Db.personal().merge 'rankings', roundId, resObj
+        Db.personal(Plugin.userId()).merge 'rankings', roundId, resObj

 exports.client_addQuestion = (question) !->
     pos = Db.shared.incr 'questionsLength'


Tiny fix for getMatchedTagsByName method in post model.

--- src/model/post.coffee
+++ src/model/post.coffee
@@ -28,6 +28,8 @@
 # @return array
 ###
 getMatchedTagsByName = (name) ->
+  return [] unless name
+
   tagsData = await tag.findAll
     raw: on
     limit: 5


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/rectangle.coffee
+++ src/rectangle.coffee
@@ -34,3 +34,80 @@
   (exports ? this)["Rectangle"] = Rectangle
 )()

+(->
+  Rectangle = ({x, y, halfWidth, halfHeight}) ->
+
+    return {
+      __proto__: Rectangle::
+
+      x: x || 0
+      y: y || 0
+
+      halfWidth: halfWidth || 0
+      halfHeight: halfHeight || 0
+    }
+
+  Rectangle:: =
+    contains: (rectangle) ->
+      rectangle.top >= @top and
+      rectangle.bottom <= @bottom and
+      rectangle.left >= @left and
+      rectangle.right <= @right
+
+    center: ->
+      Point(@x, @y)
+
+    equal: (other) ->
+      @x is other.x and
+      @y is other.y and
+      @halfWidth is other.halfWidth and
+      @halfWidth is other.halfHeight
+
+    grow: (x, y) ->
+      amount = x unless y?
+
+      if amount
+        return Rectangle
+          x: @x
+          y: @y
+          halfWidth: @halfWidth + amount
+          halfHeight: @halfHeight + amount
+      else
+        return Rectangle
+          x: @x
+          y: @y
+          halfWidth: @halfWidth + x
+          halfHeight: @halfHeight + y
+
+    shrink: (x, y) ->
+      if y?
+        @grow(-x, -y)
+      else
+        @grow(-x, -x)
+
+    overlaps: (rectangle) ->
+      @top < rectangle.bottom and
+      @right > rectangle.left and
+      @bottom > rectangle.top and
+      @left < rectangle.right
+
+    outsideOf: (rectangle) ->
+      not @contains(rectangle) and
+      not @overlaps(rectangle)
+
+  Rectangle::__defineGetter__ 'left', ->
+    @x - @halfWidth
+
+  Rectangle::__defineGetter__ 'right', ->
+    @x + @halfWidth
+
+  Rectangle::__defineGetter__ 'top', ->
+    @y - @halfHeight
+
+  Rectangle::__defineGetter__ 'bottom', ->
+    @y + @halfHeight
+
+  (exports ? this)["Rectangle"] = Rectangle
+)()
+
+


remove nullDelay hack from selection

remove nullDelay hack from selection

--- src/selection.coffee
+++ src/selection.coffee
@@ -8,10 +8,8 @@

   constructor: (@doc, @emitter) ->
     @root = @doc.domNode
-    @focus = false
     @range = new Range(0, 0)
-    @nullDelay = false
-    this.update('silent')
+    this.update(@emitter.constructor.sources.SILENT)

   checkFocus: ->
     return document.activeElement == @root
@@ -99,17 +97,8 @@
   update: (source) ->
     focus = this.checkFocus()
     range = this.getRange(true)
-    emit = source != 'silent' and (_.isEqual(range, @range) or focus != @focus)
-    toEmit = if focus then range else null
-    # If range changes to null, require two update cycles to update and emit
-    if toEmit == null and source == 'user' and !@nullDelay
-      @nullDelay = true
-    else
-      @nullDelay = false
-      @range = range
-      @focus = focus
-      # Set range before emitting to prevent infinite loop if listeners call quill.getSelection()
-      @emitter.emit(@emitter.constructor.events.SELECTION_CHANGE, toEmit, source) if emit
+    @range = range
+    @emitter.emit(@emitter.constructor.events.SELECTION_CHANGE, toEmit, source) if emit

   _decodePosition: (node, offset) ->
     if dom(node).isElement()


Split platform specific keybindings

Split platform specific keybindings

--- keymaps/spell-check.cson
+++ keymaps/spell-check.cson
@@ -1,2 +1,5 @@
-'.editor':
-  'meta-:': 'editor:correct-misspelling'
+'.platform-darwin .editor':
+  'cmd-:': 'editor:correct-misspelling'
+
+'.platform-win32 .editor':
+  'ctrl-:': 'editor:correct-misspelling'


Fix #209

Fix #209

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -79,7 +79,7 @@
   #         'saved' and `destroyed` events of the underlaying text-buffer.
   showFile: ->
     atom.workspace
-      .open(@filePath(), split: 'left', searchAllPanes: true)
+      .open(@filePath(), searchAllPanes: true)
       .done (textEditor) =>
         if atom.config.get('git-plus.openInPane')
           @splitPane(atom.config.get('git-plus.splitPane'), textEditor)
@@ -125,20 +125,19 @@
         if @andPush
           new GitPush(@repo)
         @isAmending = false
-        @destroyActiveEditorView()
+        @destroyCommitEditor()
         # Activate the former active pane.
         @currentPane.activate() if @currentPane.alive
         git.refresh()

-      stderr: (err) => @destroyActiveEditorView()
+      stderr: (err) => @destroyCommitEditor()

-  # Public: Destroys the active EditorView to trigger our cleanup method.
-  destroyActiveEditorView: ->
-    if atom.workspace.getActivePane().getItems().length > 1
-      atom.workspace.destroyActivePaneItem()
-    else
-      atom.workspace.destroyActivePane()
+  destroyCommitEditor: ->
     @cleanup()
+    atom.workspace.getTextEditors().some (editor) ->
+      if editor.getURI().includes 'COMMIT_EDITMSG'
+        editor.destroy()
+        return true

   # Public: Undo the amend
   #
@@ -155,7 +154,7 @@
         @isAmending = false

         # Destroying the active EditorView will trigger our cleanup method.
-        @destroyActiveEditorView()
+        @destroyCommitEditor()

   # Public: Cleans up after the EditorView gets destroyed.
   cleanup: ->


Improve git-commit.coffee behavior

Improve git-commit.coffee behavior

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -2,65 +2,61 @@
 git = require '../git'
 StatusView = require '../views/status-view'

+amend = null
 currentPane = atom.workspace.getActivePane()
+file = '.git/COMMIT_EDITMSG'
 dir = atom.project.getRepo()?.getWorkingDirectory() ? atom.project.getPath()
-commitFilePath = -> dir + '/.git/COMMIT_EDITMSG'
-commitEditor = null
-watcher = null
-amendMsg = ""

-gitCommit = (_amendMsg="") ->
-  amendMsg = _amendMsg
+gitCommit = (_amend='') ->
+  if amend?
+    atom.workspace.open(file, activatePane: true, searchAllPanes: true)
+    return
+
+  amend = _amend
   git.cmd(
     args: ['status'],
-    stdout: (data) -> prepFile data.toString()
+    stdout: (data) -> prepFile data, amend
   )

 # FIXME?: maybe I shouldn't use the COMMIT file in .git/
-prepFile = (text) ->
-  watcher.close() if watcher?
-  # format the text to be ignored in the commit message
-  text = text.replace(/\s*\(.*\)\n/g, '')
-  text = text.trim().replace(/\n/g, "\n# ")
-  # in order to make sure each line doesn't start with a space, the preceding
-  #   line should end with a backslash
-  fs.writeFileSync commitFilePath(),
-     """#{amendMsg}
+prepFile = (status, amend) ->
+  # format the status to be ignored in the commit message
+  status = status.replace(/\s*\(.*\)\n/g, '')
+  status = status.trim().replace(/\n/g, "\n# ")
+  fs.writeFileSync "#{dir}/#{file}",
+     """#{amend}
       # Please enter the commit message for your changes. Lines starting
       # with '#' will be ignored, and an empty message aborts the commit.
       # Remove hyphen(-) and update commit message as necessary for amend.
       #
-      # #{text}""",
-    flag: 'w+'
+      # #{status}"""
   showFile()

 showFile = ->
-  split = ''
-  split = 'right' if atom.config.get 'git-plus.openInPane'
+  split = if atom.config.get('git-plus.openInPane') then 'right' else ''
   atom.workspace
-    .open(commitFilePath(), split: split, activatePane: true)
-    # ::open returns a promise resolving to the editor
-    .done (editor) -> commitEditor = editor
-  watcher = fs.watch commitFilePath(), (event) ->
-    commit() if event is 'change'
+    .open(file, split: split, activatePane: true, searchAllPanes: true)
+    .done (editor) ->
+      editor.buffer.on 'saved', -> commit()
+      editor.buffer.on 'destroyed', -> cleanup()

 commit = ->
-  watcher.close()
-  args = ['commit', '--cleanup=strip', "--file=#{commitFilePath()}"]
-  args.push '--amend' if amendMsg isnt ''
+  args = ['commit', '--cleanup=strip', "--file=#{file}"]
+  args.push '--amend' if amend isnt ''
   git.cmd(
     args: args,
     stdout: (data) ->
+      new StatusView(type: 'success', message: data.toString())
       if atom.workspace.getActivePane().getItems().length > 1
         atom.workspace.destroyActivePaneItem()
       else
         atom.workspace.destroyActivePane()
-      currentPane.activate()
-      new StatusView(type: 'success', message: data.toString())
-      # reset editor for commitFile
-      currentEditor = null
-      atom.workspaceView.trigger 'core:save'
       atom.project.getRepo()?.refreshStatus()
   )

+cleanup = ->
+  amend = null
+  currentPane.activate()
+  try fs.unlinkSync "#{dir}/#{file}"
+
 module.exports = gitCommit


reset thumbs on next photo

reset thumbs on next photo

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -85,9 +85,13 @@
         top = (400 - $('#photo').height())/2
         $('#photo').css 'top', top

-      $('#thumbs_up').css 'background', 'url(/img/thumbs_up20.png)'
-      $('#thumbs_down').css 'background', 'url(/img/thumbs_down20.png)'
-
+      if active_thumb == 1
+        $('#thumbs_up').css 'background', 'url(/img/thumbs_up20.png)'
+        active_thumb = 0
+      if active_thumb == -1
+        $('#thumbs_down').css 'background', 'url(/img/thumbs_down20.png)'
+        active_thumb = 0
+
   show_right_answere = ->
     #show the right answere
     if curr_photo.country


Alternate titles and descriptions in s3 export

--- scripts/daily_upload_s3.coffee
+++ scripts/daily_upload_s3.coffee
@@ -55,10 +55,15 @@
   'description'
   'sections'
   'hero_section'
+  'social_title'
+  'social_image'
+  'social_description'
+  'search_title'
+  'search_description'
+  'email_title'
 ]
-projections = object attrs, attrs.map -> 1

-db.articles.find({ published: true }, projections).toArray (err, articles) ->
+db.articles.find({ published: true }).toArray (err, articles) ->

   stringify = (arr) ->
     return null unless arr
@@ -73,6 +78,8 @@
     sections = a.sections.map (section) => pick section, 'type'
     if a.hero_section
       hero = stringify(JSON.stringify(pick a.hero_section, 'type'))
+    if headline = a.email_metadata?.headline
+      email_title = stringify headline
     row = [
       a._id
       a.author_id
@@ -104,6 +111,12 @@
       stringify a.description
       stringify(JSON.stringify(sections))
       hero
+      stringify a.social_title
+      stringify a.social_image
+      stringify a.social_description
+      stringify a.search_title
+      stringify a.search_description
+      email_title
     ].join(',')
     csv.push row


Added reload method that forces a model or collection to refetch

This could be used when a model has updated remotely or when the user
logs off and then back on

--- scripts/mixins/loadable.coffee
+++ scripts/mixins/loadable.coffee
@@ -34,4 +34,19 @@
     # _loadComplex: (fetchPromise) -> fetchPromise

+    # Force a `Backbone.Collection` or `Backbone.Model` to reload its contents.
+    # If in the middle of a `load` it waits until the load completes.
+    # Returns a Promise when the reload complates.
+    reload: () ->
+      # Finish reloading if loading has already started
+      if @_loading
+        return @_loading.then () =>
+          delete @_loading
+          return @reload()
+
+      else
+        # For collections reset the contents to nothing
+        @reset?()
+        return @load()
+
   return loadableMixin


Don't focus non-focusable node.

Don't focus non-focusable node.

Try the following....

- load page
- `/`
- type some junk which matches nothing
- `Escape`

We expect to leave find mode.  Instead, we have to hit escape again to
get out of find mode.

Here, `focusNode.focus()` is not a function.

--- content_scripts/hud.coffee
+++ content_scripts/hud.coffee
@@ -66,7 +66,7 @@

     focusNode = DomUtils.getSelectionFocusElement()
     document.activeElement?.blur()
-    focusNode?.focus()
+    focusNode?.focus?()

     if exitEventIsEnter
       handleEnterForFindMode()


fix typo on server task file

fix typo on server task file

I'm not sure if that is correct, but it seems to me that you meant "by" instead of "be".

--- tasks/server.coffee
+++ tasks/server.coffee
@@ -7,7 +7,7 @@
 Configuration:
 "base" - the path from which to serve static assets from (this should almost always be left to the default value of "generated")
 "web.port" - the port from which to run the development server (defaults to 8000, can be overridden with ENV variable WEB_PORT)
-"apiProxy.port" - the port of the server running an API we want to proxy (does not proxy be default, can be overridden with ENV variable API_PORT)
+"apiProxy.port" - the port of the server running an API we want to proxy (does not proxy by default, can be overridden with ENV variable API_PORT)
 "apiProxy.enabled" - set to true to enable API proxying; if Lineman can't respond to a request, it will forward it to the API proxy
 "apiProxy.host" - the host to which API requests should be proxy, defaults to `localhost`)"
 "apiProxy.prefix" - an api prefix, to be used in conjunction with server.pushState to correctly identify requests that should go to the apiProxy"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -27,7 +27,7 @@

 someOtherObject.meter 'turbo'
 # => Sets up a turbo meter that will be drawn during the someOtherObject overlay event.
-# Since neither turbo maxTurbo were provided, they are both
+# Since neither turbo maxTurbo were provided, they are both set to 100

 # now player has all the methods provided by this module


reverse on coffee notation

reverse on coffee notation

--- BackofficeBundle/Resources/public/coffee/orchestraLib.coffee
+++ BackofficeBundle/Resources/public/coffee/orchestraLib.coffee
@@ -31,7 +31,7 @@

 displayMenu = (route, refresh) ->
   selectedPath = "#" + (route || Backbone.history.fragment)
-  refresh = refresh || route?
+  refresh = refresh || (typeof route == "undefined")
   $.ajax
     url: $("#left-panel nav").data("url")
     type: "GET"


Update skeleton

Update skeleton

--- skeletons/skeleton.coffee
+++ skeletons/skeleton.coffee
@@ -1,7 +1,7 @@
 Template.BASENAME.helpers
 #  helper: ->

-Template.BASENAME.rendered = ->
+Template.BASENAME.onRendered ->

 Template.BASENAME.events
 #  "click .selector": (event, template) ->


atom: add jasmine snippets

atom: add jasmine snippets


refactoring all of the collection helpers to have their functions separate from the handlebars helper registration

--- src/helpers/helpers-collections.coffee
+++ src/helpers/helpers-collections.coffee
@@ -1,209 +1,174 @@
 # Local deps
 Utils = require '../utils/utils'

+# First: Returns the first item in a collection.
 module.exports.first = first = (array, count) ->
   if Utils.isUndefined(count) then array[0] else array.slice 0, count

+# With first: Use the first item in a collection inside a block.
+module.exports.withFirst = withFirst = (array, count, options) ->
+  if Utils.isUndefined count
+    options = count
+    options.fn array[0]
+  else
+    array = array.slice 0, count
+    result = ''
+    for item of array then result += options.fn array[item]
+    result
+
+# Last: Returns the last item in a collection. Opposite of `first`.
+module.exports.last = last = (array, count) ->
+  if Utils.isUndefined(count) then array[array.length - 1] else array.slice -count
+
+# With Last: Use the last item in a collection inside a block. Opposite of `withFirst`.
+module.exports.withLast = withLast = (array, count, options) ->
+  if Utils.isUndefined count
+    options = count
+    options.fn array[array.length - 1]
+  else
+    array = array.slice -count
+    result = ''
+    for item of array then result += options.fn array[item]
+    result
+
+# After: Returns all of the items in the collection after the specified count.
+module.exports.after = after = (array, count) ->
+  array.slice count
+
+# With After: Use all of the items in the collection after the specified
+# count inside a block.
+module.exports.withAfter = withAfter = (array, count, options) ->
+  array = array.slice count
+  result = ''
+  for item of array then result += options.fn array[item]
+  result
+
+# Before: Returns all of the items in the collection before the specified count.
+# Opposite of `after`.
+module.exports.before = before = (array, count) ->
+  array.slice 0, -count
+
+# With Before: Use all of the items in the collection before the specified
+# count inside a block. Opposite of `withAfter`.
+module.exports.withBefore = withBefore = (array, count, options) ->
+  array = array.slice 0, -count
+  result = ''
+  for item of array then result += options.fn array[item]
+  result
+
+# Join: Joins all elements of a collection into a string using a separator if specified.
+module.exports.join = join = (array, separator) ->
+  array.join if Utils.isUndefined(separator) then ' ' else separator
+
+module.exports.sort = sort = (array, field) ->
+  if Utils.isUndefined field
+    array.sort()
+  else
+    array.sort (a, b) -> a[field] > b[field]
+
+module.exports.withSort = withSort = (array, field, options) ->
+  result = ''
+  if Utils.isUndefined field
+    options = field
+    array = array.sort()
+    result += options.fn(item) for item in array
+  else
+    array = array.sort (a, b) -> a[field] > b[field]
+    result += options.fn(array[item]) for item of array
+  result
+
+module.exports.length = length = (array) ->
+  array.length
+
+module.exports.lengthEqual = lengthEqual = (array, length, options) ->
+  if array.length is length then options.fn(@) else options.inverse(@)
+
+module.exports.empty = empty = (array, options) ->
+  if array.length <= 0 then options.fn(@) else options.inverse(@)
+
+module.exports.any = any = (array, options) ->
+  if array.length > 0 then options.fn(@) else options.inverse(@)
+
+module.exports.inArray = inArray = (array, value, options) ->
+  if array.indexOf(value) isnt -1  then options.fn(@) else options.inverse(@)
+
+###
+Similar to #each helper, but treats array-like objects as arrays
+(i.e. objects with a `.length` property which is a number)
+rather than objects. This lets us iterate over our Collection's.
+###
+# module.exports.iterate = iterate = (context, options) ->
+#   fn = options.fn
+#   inverse = options.inverse
+#   i = 0
+#   ret = ""
+#   data = undefined
+#   data = Handlebars.createFrame(options.data)  if options.data
+#   if context and typeof context is "object"
+#     if typeof context.length is "number"
+#       j = context.length
+
+#       while i < j
+#         data.index = i  if data
+#         ret = ret + fn(context[i],
+#           data: data
+#         )
+#         i++
+#     else
+#       for key of context
+#         if context.hasOwnProperty(key)
+#           data.key = key  if data
+#           ret = ret + fn(context[key],
+#             data: data
+#           )
+#           i++
+#   ret = inverse(this)  if i is 0
+#   ret
+
+module.exports.eachIndex = eachIndex = (array, options) ->
+  result = ''
+  for value, index in array
+    result += options.fn item: value, index: index
+  result
+
+# Handlebars block helper to enumerate properties in an object
+module.exports.eachProperty = eachProperty = (obj, options) ->
+  result = ''
+  for key, value of obj
+    result += options.fn key: key, value: value
+  result
+
+# Arrayify: data gets passed in from *.yml as a string, like "foo, bar, baz"
+# we need to convert this to an ES Array of strings to avoid reference errors
+#
+# Credit: https://github.com/operasoftware/shinydemos/blob/master/lib/compiler.js
+module.exports.arrayify = arrayify = (data) ->
+  result = data.split(",").map((tag) ->
+    "\"" + tag + "\""
+  )
+  result
+
 module.exports.register = (Handlebars, options) ->

-  # First: Returns the first item in a collection.
   Handlebars.registerHelper 'first', first
-
-  # With first: Use the first item in a collection inside a block.
-  Handlebars.registerHelper 'withFirst', (array, count, options) ->
-      if Utils.isUndefined count
-          options = count
-          options.fn array[0]
-      else
-          array = array.slice 0, count
-          result = ''
-          for item of array then result += options.fn array[item]
-          result
-
-
-  # Last: Returns the last item in a collection. Opposite of `first`.
-  Handlebars.registerHelper 'last', (array, count) ->
-      if Utils.isUndefined(count) then array[array.length - 1] else array.slice -count
-
-
-  # With Last: Use the last item in a collection inside a block. Opposite of `withFirst`.
-  Handlebars.registerHelper 'withLast', (array, count, options) ->
-      if Utils.isUndefined count
-          options = count
-          options.fn array[array.length - 1]
-      else
-          array = array.slice -count
-          result = ''
-          for item of array then result += options.fn array[item]
-          result
-
-
-  # After: Returns all of the items in the collection after the specified count.
-  Handlebars.registerHelper 'after', (array, count) ->
-      array.slice count
-
-
-  # With After: Use all of the items in the collection after the specified
-  # count inside a block.
-  Handlebars.registerHelper 'withAfter', (array, count, options) ->
-      array = array.slice count
-      result = ''
-      for item of array then result += options.fn array[item]
-      result
-
-
-  # Before: Returns all of the items in the collection before the specified count.
-  # Opposite of `after`.
-  Handlebars.registerHelper 'before', (array, count) ->
-      array.slice 0, -count
-
-
-  # With Before: Use all of the items in the collection before the specified
-  # count inside a block. Opposite of `withAfter`.
-  Handlebars.registerHelper 'withBefore', (array, count, options) ->
-      array = array.slice 0, -count
-      result = ''
-      for item of array then result += options.fn array[item]
-      result
-
-
-  # Join: Joins all elements of a collection into a string using a separator if specified.
-  Handlebars.registerHelper 'join', (array, separator) ->
-      array.join if Utils.isUndefined(separator) then ' ' else separator
-
-
-  #
-  Handlebars.registerHelper 'sort', (array, field) ->
-      if Utils.isUndefined field
-          array.sort()
-      else
-          array.sort (a, b) -> a[field] > b[field]
-
-  #
-  # Handlebars "join" block helper that supports arrays of objects or strings.
-  # (implementation found here: https://github.com/wycats/handlebars.js/issues/133)
-  #
-  # If "delimiter" is not speficified, then it defaults to ",".
-  # You can use "start", and "end" to do a "slice" of the array.
-  #
-  # Use with objects:
-  # {{#join people delimiter=" and "}}{{name}}, {{age}}{{/join}}
-  #
-  # Use with arrays:
-  # {{join jobs delimiter=", " start="1" end="2"}}
-  #
-  # Handlebars.registerHelper "join", (items, block) ->
-  #   delimiter = block.hash.delimiter or ","
-  #   start = start = block.hash.start or 0
-  #   len = (if items then items.length else 0)
-  #   end = block.hash.end or len
-  #   out = ""
-  #   end = len  if end > len
-  #   if "function" is typeof block
-  #     i = start
-  #     while i < end
-  #       out += delimiter  if i > start
-  #       if "string" is typeof items[i]
-  #         out += items[i]
-  #       else
-  #         out += block(items[i])
-  #       i++
-  #     out
-  #   else
-  #     [].concat(items).slice(start, end).join delimiter
-
-
-  #
-  Handlebars.registerHelper 'withSort', (array, field, options) ->
-      result = ''
-
-      if Utils.isUndefined field
-          options = field
-          array = array.sort()
-          result += options.fn(item) for item in array
-      else
-          array = array.sort (a, b) -> a[field] > b[field]
-          result += options.fn(array[item]) for item of array
-
-      result
-
-  Handlebars.registerHelper 'length', (array) ->
-      array.length
-
-  Handlebars.registerHelper 'lengthEqual', (array, length, options) ->
-      if array.length is length then options.fn(@) else options.inverse(@)
-
-  Handlebars.registerHelper 'empty', (array, options) ->
-      if array.length <= 0 then options.fn(@) else options.inverse(@)
-
-  Handlebars.registerHelper 'any', (array, options) ->
-      if array.length > 0 then options.fn(@) else options.inverse(@)
-
-  Handlebars.registerHelper 'inArray', (array, value, options) ->
-      if array.indexOf(value) isnt -1  then options.fn(@) else options.inverse(@)
-
-
-  ###
-  Similar to #each helper, but treats array-like objects as arrays
-  (i.e. objects with a `.length` property which is a number)
-  rather than objects. This lets us iterate over our Collection's.
-  ###
-  # Handlebars.registerHelper "iterate", (context, options) ->
-  #     fn = options.fn
-  #     inverse = options.inverse
-  #     i = 0
-  #     ret = ""
-  #     data = undefined
-  #     data = Handlebars.createFrame(options.data)  if options.data
-  #     if context and typeof context is "object"
-  #       if typeof context.length is "number"
-  #         j = context.length
-
-  #         while i < j
-  #           data.index = i  if data
-  #           ret = ret + fn(context[i],
-  #             data: data
-  #           )
-  #           i++
-  #       else
-  #         for key of context
-  #           if context.hasOwnProperty(key)
-  #             data.key = key  if data
-  #             ret = ret + fn(context[key],
-  #               data: data
-  #             )
-  #             i++
-  #     ret = inverse(this)  if i is 0
-  #     ret
-
-
-
-  #
-  Handlebars.registerHelper 'eachIndex', (array, options) ->
-      result = ''
-      for value, index in array
-          result += options.fn item: value, index: index
-      result
-
-
-  # Handlebars block helper to enumerate properties in an object
-  Handlebars.registerHelper 'eachProperty', (obj, options) ->
-      result = ''
-      for key, value of obj
-          result += options.fn key: key, value: value
-      result
-
-
-  # Arrayify: data gets passed in from *.yml as a string, like "foo, bar, baz"
-  # we need to convert this to an ES Array of strings to avoid reference errors
-  #
-  # Credit: https://github.com/operasoftware/shinydemos/blob/master/lib/compiler.js
-  Handlebars.registerHelper "arrayify", (data) ->
-    result = data.split(",").map((tag) ->
-      "\"" + tag + "\""
-    )
-    result
-
-
+  Handlebars.registerHelper 'withFirst', withFirst
+  Handlebars.registerHelper 'last', last
+  Handlebars.registerHelper 'withLast', withLast
+  Handlebars.registerHelper 'after', after
+  Handlebars.registerHelper 'withAfter', withAfter
+  Handlebars.registerHelper 'before', before
+  Handlebars.registerHelper 'withBefore', withBefore
+  Handlebars.registerHelper 'join', join
+  Handlebars.registerHelper 'sort', sort
+  Handlebars.registerHelper 'withSort', withSort
+  Handlebars.registerHelper 'length', length
+  Handlebars.registerHelper 'lengthEqual', lengthEqual
+  Handlebars.registerHelper 'empty', empty
+  Handlebars.registerHelper 'any', any
+  Handlebars.registerHelper 'inArray', inArray
+  # Handlebars.registerHelper "iterate", iterate
+  Handlebars.registerHelper 'eachIndex', eachIndex
+  Handlebars.registerHelper 'eachProperty', eachProperty
+  Handlebars.registerHelper "arrayify", arrayify

   @


Add a timeout to preroll attempts

Add a timeout to preroll attempts

Fighting a condition where we seemed to be stacking up requests that didn't
get turned into anything until they all eventually timed out.

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -363,6 +363,12 @@

         conn = req.connection

+        # refuse to wait longer than 250ms
+        req_t = setTimeout =>
+            console.debug "Preroll timeout reached for #{count}."
+            conn_pre_abort()
+        , 250
+
         console.debug "firing preroll request", count
         req = http.get opts, (rres) =>
             console.debug "got preroll response ", count, rres.statusCode
@@ -375,6 +381,8 @@

                 buffers = []
                 buf_len = 0
+
+                clearTimeout req_t

                 rres.on "data", (chunk) =>
                     buffers.push chunk
@@ -410,9 +418,14 @@
         # shut down and we should abort the request

         conn_pre_abort = =>
+            console.debug "conn_pre_abort called. Destroyed? ", conn.destroyed
+
             if conn.destroyed
                 console.debug "aborting preroll ", count
                 req.abort()

+            clearTimeout req_t
+
         conn.once "close", conn_pre_abort
         conn.once "end", conn_pre_abort
+


findAnswerPlayerを解答権保持者探しにも使う

findAnswerPlayerを解答権保持者探しにも使う

--- src/progress/index.coffee
+++ src/progress/index.coffee
@@ -54,9 +54,7 @@

     subscribe 'try-answer', (playerId) ->
       # 誰か一人でも解答権を持っている場合は、処理を進めない
-      hasAnswer = @state.players.some (val, idc, arr) ->
-        return val.isAnswer
-      if hasAnswer
+      if @findAnswerPlayer() != null
         console.debug('already answers')
         return
       players_ = @state.players.map (player) ->


正解/誤答時に問題数経過

正解/誤答時に問題数経過

--- src/progress/index.coffee
+++ src/progress/index.coffee
@@ -69,12 +69,14 @@
       answerPlayer = @findAnswerPlayer()
       answerPlayer.isAnswer = false
       answerPlayer.doRight()
+      @state.quizCount++;
       @update (state) => state

     subscribe 'answer-wrong', ->
       answerPlayer = @findAnswerPlayer()
       answerPlayer.isAnswer = false
       answerPlayer.doWrong()
+      @state.quizCount++;
       @update (state) => state

     subscribe 'reset-answer', ->


Remove unnecessary logging

Remove unnecessary logging

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -19,44 +19,31 @@

     # save the DOM
     if this.original_document == ""
-      console.log("Annotran: Saving original DOM state")
       this.original_document = $("body").clone()

     if this.state == "clean"
-      console.log("Document is clean, not performing any reset.")
       return

     this.state = "clean"

     # blank the DOM
     if this.original_document != $("body")
-      console.log("Annotran: blanking DOM but preserving iframe.")

       for ele in $("body").children()
         if ele.nodeName != undefined and ele.nodeName != "IFRAME" and ele.nodeName != "SCRIPT" and ele.nodeName != "DIV"
-          console.log("Removing: " + ele.nodeName)
           ele.remove()
         else
           if $(ele).hasClass("annotator-notice") or $(ele).hasClass("annotator-frame") or $(ele).hasClass("annotator-adder")
-            console.log("Saving: " + ele.nodeName)
             annotatorComponents.push ele
           else if ele.nodeName != "SCRIPT"
-            console.log("Removing: " + ele.nodeName)
             ele.remove()
-
-      # restore the DOM
-      console.log("Annotran: restoring all of DOM except iframe.")
-      console.log($(this.original_document).children().length + " items to process.")

       for ele in $(this.original_document).children()
         if ele.nodeName != undefined and ele.nodeName != "IFRAME" and ele.nodeName != "SCRIPT" and ele.nodeName != "DIV"
-          console.log("Re-adding: " + ele.nodeName)
           $(annotatorComponents[0]).before($(ele).clone())
         else
           if $(ele).hasClass("annotator-notice") or $(ele).hasClass("annotator-frame") or $(ele).hasClass("annotator-adder")
-            console.log("Not re-adding: " + ele.nodeName)
           else if ele.nodeName != "SCRIPT"
-            console.log("Re-adding: " + ele.nodeName)
             $(annotatorComponents[0]).before($(ele).clone())

       return null
@@ -64,7 +51,6 @@
   multipleSubstitution: (annotations = []) =>
     # iterate over the annotations _in reverse order_ so that all the XPATHs still work
     # in other words, this function takes a list of annotations in top to bottom order and works from the end upwards
-    console.log("Initializing substitution")
     this.state = "dirty"

     for data in annotations.reverse()


Fixed see more link.

Fixed see more link.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -608,9 +608,9 @@
     e.preventDefault()
     $(e.target).hide()
     if $(e.target).next('div')[0]
-      $(e.target).next('div').show()
+      $(e.target).next('div').removeClass('hide')
     else
-      $(e.target).parent().next('div').show()
+      $(e.target).parent().next('div').removeClass('hide')

   checkIfSignatureIsNeeded: (article_type) =>

@@ -705,9 +705,9 @@
     if article_lines.length > preview
       preview_mode = true
       if article_lines[preview] is ''
-        article_lines.splice( preview, 0, '----SEEMORE----' )
+        article_lines.splice( preview, 0, '-----SEEMORE-----' )
       else
-        article_lines.splice( preview + 1, 0, '----SEEMORE----' )
+        article_lines.splice( preview - 1, 0, '-----SEEMORE-----' )
       @article['html'] = article_lines.join("\n")
     @article['html'] = window.linkify( @article['html'] )
     notify = '' + App.i18n.translateContent('See more') + ''
@@ -715,7 +715,7 @@
     # preview mode
     if preview_mode
       @article_changed = false
-      @article['html'] = @article['html'].replace /^\n{0,10}----SEEMORE----\n/m, (match) =>
+      @article['html'] = @article['html'].replace /^-----SEEMORE-----\n/m, (match) =>
         @article_changed = true
         notify + '
'
       if @article_changed
@@ -729,7 +729,6 @@
         notify + '
' + match
       if @article_changed
         @article['html'] = @article['html'] + '
'
-

   actionRow: ->
     if @isRole('Customer')


Use real name instead of username in parens

--- app/assets/javascripts/components/overview/inline_users.cjsx
+++ app/assets/javascripts/components/overview/inline_users.cjsx
@@ -9,7 +9,7 @@
     user_list = @props.users.map (user, index) ->
       link = "https://en.wikipedia.org/wiki/User:#{user.wiki_id}"
       if user.real_name?
-        extra_info = " (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
+        extra_info = " (#{user.real_name}#{if user.email? then " / " + user.email else ""})"
       else
         extra_info = ''
       extra_info = extra_info + ', ' unless index == last_user_index


Fix indeterminate

Fix indeterminate

--- app/assets/javascripts/georgia/prototypes/checkbox.js.coffee
+++ app/assets/javascripts/georgia/prototypes/checkbox.js.coffee
@@ -9,7 +9,7 @@
     @checkboxes.bind('click', @update)

   update: (event) =>
-    element = $("##{event.target.id}")
+    element = $(event.currentTarget)
     @updateCheckbox(element)
     @updateSelectAll()


Show warnings when a coffeescript code block fails to compile

--- layout.coffee
+++ layout.coffee
@@ -348,8 +348,11 @@
           if not window.CoffeeScript
             console.warn 'missing coffee-script.js include'
             return
+          try
           # console.log 'compiling coffee-script', script
-          coffeeCache[script] = CoffeeScript.compile(script, bare: true) if script
+            coffeeCache[script] = CoffeeScript.compile(script, bare: true) if script
+          catch error
+            showWarnings(["error #{error} compiling script\r\n#{script}"])
           # console.log 'compiled coffee-script', script
           # console.log coffeeCache
           # return coffeeCache[script]


improve reactive capabilities

improve reactive capabilities

--- templates:tabs.coffee
+++ templates:tabs.coffee
@@ -18,26 +18,44 @@

       created = ->
         self = this
-        activeTab = null
         check(self.data.tabs, Array)
         check(self.data.activeTab, Match.Optional(String))

+        # Init--set first tab if no specified active tab
         if self.data.activeTab
-          activeTab = self.data.activeTab # should use slug for routes compatibility
-        else
+          activeTab = self.data.activeTab
+        else
           activeTab = self.data.tabs[0]

+        # Set up reactive data structures
         self._tabs = new ReactiveArray(self.data.tabs)
         self._activeTab = new Blaze.ReactiveVar(activeTab)

+        # Work with reactive data structures
+        self.setTabs = (array) ->
+          if Match.test(array, Array)
+            self._tabs.set(array)
+
+        self.setActiveTab = (tab) ->
+          if Match.test(tab, Match.ObjectIncluding({
+            name: String
+            slug: String
+          }))
+
+            self._activeTab.set(tab)
+
+        self.isActiveSlug = (slug) ->
+          self._activeTab.get()?.slug is slug
+
       rendered = ->
         self = this
-        tabs = self._tabs.get()
         contentBlocks = self.findAll('.tabs-content-container > div')

         # Add data-tab attribute to all tabbed content areas
-        for tab, i in tabs
-          ($ contentBlocks[i]).addClass('tabs-content').attr('data-tab', tab.slug)
+        self.autorun ->
+          tabs = self._tabs.get()
+          for tab, i in tabs
+            ($ contentBlocks[i]).addClass('tabs-content').attr('data-tab', tab.slug)

         # Sync corresponding content areas with active tab
         self.autorun ->
@@ -52,8 +70,15 @@

       helpers = {
         activeTab: (slug) ->
-          if Template.instance()._activeTab.get()?.slug is slug
+          if Template.instance().isActiveSlug(slug)
             return 'active'
+
+        # Use these trackers if you want to reactively change tabs/activeTab from the outside
+        trackActiveTab: (activeTab) ->
+          Template.instance().setActiveTab(activeTab)
+
+        trackTabs: (tabs) ->
+          Template.instance().setTabs(tabs)
       }

       # Put it all together!


Better bahaviour for when FILE in `zappa FILE` does not exist. No longer simply dies with an exception but prints a more friendly error before a process.exit(-1). Untested, but all examples appear to function correctly against the change.

--- bin/zappa.coffee
+++ bin/zappa.coffee
@@ -73,11 +73,18 @@

 if args.length > 0
   file = args[0]
-  if options.compile then compile file
-  else
-    if options.watch
-      remove_watch_option()
-      spawn_child()
-      watch file
+
+  path.exists file, (exists) ->
+    if !exists
+      console.log "#{file} not found."
+      process.exit -1
+
+    if options.compile
+      compile file
     else
-      zappa.run_file file, options
+      if options.watch
+        remove_watch_option()
+        spawn_child()
+        watch file
+      else
+        zappa.run_file file, options


[master] Fix

[master] Fix

--- scraper/get_comment_count.coffee
+++ scraper/get_comment_count.coffee
@@ -73,7 +73,7 @@
           storePayload createDate
         else
           requestPromise Url.resolve('http://guba.eastmoney.com/', threadUrl)
-          .then ->
+          .then (body) ->
             html = cherrio.load body
             [match] = /\d{4}-\d{2}-\d{2}/.exec html('.zwfbtime').text()
             createDate = moment match, 'YYYY-MM-DD'


remove filters since they don't work

remove filters since they don't work

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -13,12 +13,6 @@
   map = d3.select('body').append('svg')
     .attr('width', width)
     .attr('height', height)
-
-  map.append('defs').append('filter')
-    .attr('id', 'blend')
-  .append('feBlend')
-    .attr('mode', 'screen')
-    .attr('in1', 'BackgroundImage')

   tip = d3.tip().attr('class', 'd3-tip').html (d) ->
     if d.properties?


fix behavior / add debug feature

fix behavior / add debug feature

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -19,6 +19,10 @@
     return f

 this.iris = {
+  const: { # TODO
+    FILTER_NORMAL: 0x0001
+    FILTER_ONLY_ACTIVE: 0x0002
+  }
   config : {
     fixture : {
       wall : new shapeConfig(1, 1, 0, 0.8)
@@ -47,6 +51,9 @@
           new shapeConfig(1, 1, 0.5, 0.8)
           new shapeConfig(1, 1, 0.5, 0.8)
         ]
+      ]
+      color: [
+        "#FF0000"
       ]
     }
   }
@@ -75,13 +82,16 @@
           # circle
           fixture.shape = new b2CircleShape
           fixture.shape.SetRadius(config.size)
-      #fixture.filter.maskBits = 2
+      #fixture.filter.
       body = new b2BodyDef
       body.angle = angle
-      body.type = b2Body.b2_kinematicBody
-      body.userData = {type: "shape", data: undefined} # TODO
+      body.type = b2Body.b2_dynamicBody
+      body.userData = {type: "shape", state: 0} # TODO
       body.position.Set(position*20 + 2, 5) # x: 2-22
-      @world.CreateBody(body).CreateFixture(fixture)
+      b = @world.CreateBody(body)
+      b.CreateFixture(fixture)
+      b.SetLinearVelocity(new b2Vec2(0, 0.2)) # TODO
+      b.ApplyForce(new b2Vec2(0, -9.8 * b.GetMass()), b.GetWorldCenter()) # Cancel gravity
     shoot: (x, y, vel) ->
       fixtureConfig = window.iris.config.fixture
       config = fixtureConfig.shoot
@@ -92,7 +102,7 @@

       body = new b2BodyDef
       body.type = b2Body.b2_dynamicBody
-      body.userData = {type: "shoot", data: undefined} # TODO
+      body.userData = {type: "shoot"} # TODO
       body.position.Set(x, y)
       b = @world.CreateBody(body)
       b.CreateFixture(fixture)
@@ -103,6 +113,7 @@
       wallFixture = fixtureConfig.wall.getFixtureDef()
       wallFixture.shape = new b2PolygonShape
       wallFixture.shape.SetAsBox(0.5,8)
+      #wallFixture.filter.maskBits = 2
       wallBody = new b2BodyDef
       wallBody.type = b2Body.b2_staticBody
       wallBody.userData = {type: "wall"}
@@ -129,9 +140,9 @@
       ceilBody.position.Set(12,-4)
       @world.CreateBody(ceilBody).CreateFixture(ceilFixture)

-      @addShape(0,0,0,0,0,0.4)
+      @addShape(0,0,0,0,0,0.3)
       @addShape(1,0,0,0,0,0.5)
-      @addShape(2,0,0,0,0,0.6)
+      @addShape(2,0,0,0,0,0.7)
   }

   init : ->
@@ -150,11 +161,14 @@

   update : ->
     @field.world.Step(1/30, 10, 10) # should be customizable
+    @field.world.ClearForces()
     b = iris.field.world.GetBodyList()
     while(b)
-      if b.GetType() == 1
-        pos = b.GetPosition()
-        b.SetPosition(new b2Vec2(pos.x, pos.y + 0.01)) # should be customizable
+      data = b.GetUserData()
+      if data
+        if data.type == "shape" and data.state == 0
+          # Cancel gravity
+          b.ApplyForce(new b2Vec2(0, -9.8 * b.GetMass()), b.GetWorldCenter())
       b = b.GetNext()

     c = iris.field.world.GetContactList()
@@ -162,10 +176,8 @@
       b1 = c.GetFixtureA().GetBody()
       b2 = c.GetFixtureB().GetBody()
       if c.IsTouching()
-        if b1.GetType() == 1
-          b1.SetType(2)
-        if b2.GetType() == 1
-          b2.SetType(2)
+        @handleCollision(b1, b2)
+        @handleCollision(b2, b1)
       c = c.GetNext()

     @field.world.DrawDebugData()
@@ -182,7 +194,31 @@
       @s.moveTo(20*i,0)
       @s.lineTo(20*i,640)
       @s.stroke()
-    @field.world.ClearForces()
+    b = iris.field.world.GetBodyList()
+    while(b)
+      data = b.GetUserData()
+      if data
+        center = b.GetWorldCenter()
+        d = "cat: " + b.GetFixtureList().GetFilterData().categoryBits.toString(16) + "\n"
+        d += "mask: " + b.GetFixtureList().GetFilterData().maskBits.toString(16) + "\n"
+        for k, v of data
+          d += k + ": " + v + "\n"
+        @s.font="10px Arial"
+        @s.fillStyle = "rgba(0,0,0,1)"
+        @fillTextLine(@s, d, center.x * 20, center.y * 20)
+      b = b.GetNext()
+
+  handleCollision: (bm, bo) ->
+    data = bm.GetUserData()
+    if data
+      if data.type == "shape" and data.state == 0
+        data.state = 1
+
+  fillTextLine: (context, text, x, y) ->
+    list = text.split("\n")
+    height = context.measureText("あ").width
+    for l, i in list
+      context.fillText(l, x, y+height*i)

   click: ->
     iris.field.shoot(event.clientX / 20, event.clientY / 20, 12) # should be customizable


CADS-848 : Self employment paidMoneyWrapper was not hiding when grandparent flipped Yes/No.

--- c3/app/assets/javascripts/s_self_employment/g_selfEmploymentDates.coffee
+++ c3/app/assets/javascripts/s_self_employment/g_selfEmploymentDates.coffee
@@ -1,5 +1,5 @@
 window.initEvents = (answerY, answerN, noWrap,
-                     startY, startN, startYesWrap, startNoWrap,
+                     moreThanYearY, moreThanYearN, moreThanYearYWrap, moreThanYearNWrap,
                      haveAccountsY, haveAccountsN, haveAccountsYWrap, haveAccountsNWrap,
                      tradingYearY, tradingYearN, tradingYearYWrap,
                       paidMoneyY, paidMoneyN, paidMoneyYWrap) ->
@@ -10,16 +10,18 @@
   $("#" + answerN).on "click", ->
     showWrapper(noWrap)

-  if not $("#"+startY).prop 'checked'
-    hideWrapper(startYesWrap)
-  if not $("#"+startN).prop 'checked'
-    hideWrapper(startNoWrap)
-  $("#" + startY).on "click", ->
-    showWrapper(startYesWrap)
-    hideWrapper(startNoWrap)
-  $("#" + startN).on "click", ->
-    showWrapper(startNoWrap)
-    hideWrapper(startYesWrap)
+  if not $("#"+moreThanYearY).prop 'checked'
+    hideWrapper(moreThanYearYWrap)
+  if not $("#"+moreThanYearN).prop 'checked'
+    hideWrapper(moreThanYearNWrap)
+    hideWrapper(paidMoneyYWrap)
+  $("#" + moreThanYearY).on "click", ->
+    showWrapper(moreThanYearYWrap)
+    hideWrapper(moreThanYearNWrap)
+    hideWrapper(paidMoneyYWrap)
+  $("#" + moreThanYearN).on "click", ->
+    showWrapper(moreThanYearNWrap)
+    hideWrapper(moreThanYearYWrap)

   if not $("#"+haveAccountsY).prop 'checked'
     hideWrapper(haveAccountsYWrap)


Silently error when Model saving does not work

--- scripts/gh-book/app-local.coffee
+++ scripts/gh-book/app-local.coffee
@@ -33,7 +33,11 @@
             dataType: 'text'
             data: false
             url:"#{PATH_PREFIX}/#{path}"
-      else throw "Model sync method not supported: #{method}"
+      else
+        console.error "Model sync method not supported: #{method} for #{model.id}"
+        ret = $.Deferred()
+        ret.resolve(model.serialize())
+

     # From github-client. Parse the string if isBinary
     ret = ret.then (data, textStatus, jqXHR) ->


Cleanup

Cleanup

--- client/landing/app/Applications/Inbox.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Inbox.kdapplication/AppController.coffee
@@ -136,9 +136,7 @@
           , callback
           disownItem item for own id, item of items
         (pubInst, modal) =>
-          # log modal
           disownAll @selection, =>
-            # log "2", modal
             for own id, {item, paneView} of @selection
               item.destroy()
               paneView.destroy()


condition check is too long, split on two lines

--- source/lib/element.coffee
+++ source/lib/element.coffee
@@ -27,27 +27,28 @@
     return this

   broadcastEvent: (event, target) ->
-    if event instanceof Event and (type = event.type) and not event.aborted
-      event.start()
-      event.source ||= this
-      event.target ||= target
-      phase = (event.phase ||= 1)
+    if event instanceof Event and (type = event.type)
+      unless event.aborted or event.done or event.phase is 3
+        event.start()
+        event.source ||= this
+        event.target ||= target
+        phase = (event.phase ||= 1)

-      if event.target is this then event.phase = 2
+        if event.target is this then event.phase = 2

-      if event.phase is 1
-        @dispatchEvent event
+        if event.phase is 1
+          @dispatchEvent event

-        for child in @children
-          if event.phase is 1 and not event.aborted
-            child.broadcastEvent event
-          else break
+          for child in @children
+            if event.phase is 1 and not event.aborted
+              child.broadcastEvent event
+            else break

-      if event.phase is 2 then @dispatchEvent event
+        if event.phase is 2 then @dispatchEvent event

-      if event.source is this
-        unless event.canceled or event.aborted or event.phase is 3
-          event.callback?.call this, event
-        event.phase = 3
-        event.finish()
+        if event.source is this
+          unless event.canceled or event.aborted or event.done or event.phase is 3
+            event.callback?.call this, event
+          event.phase = 3
+          event.finish()
     return this


swallow exceptions

swallow exceptions

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -342,7 +342,7 @@
           res.send('Favicon Saved')
         )
       else
-        mkdirp(argv.status,   0777, ->
+        mkdirp(argv.status, 0777, ->
           fs.writeFile(favLoc, buf, (e) ->
             if e then throw e
             res.send('Favicon Saved')
@@ -378,7 +378,9 @@
       numFiles = files.length
       for file in files
         pagehandler.get(file, (e, page, status) ->
-          if e then throw e
+          if e
+            console.log(['pagehandler exception', e])
+            return

           # create synopsis
           if (page.synopsis?)


url experiments cleanup

url experiments cleanup

--- mrfogg-front/app/coffee/app.coffee
+++ mrfogg-front/app/coffee/app.coffee
@@ -33,8 +33,6 @@
         {templateUrl: '/views/login.html', controller:"MrLoginController"})
     $routeProvider.when('/users',
         {templateUrl: '/views/userlist.html', controller:"UsersController"})
-    $routeProvider.when('/users/:uid',
-        {templateUrl: '/views/user.html', controller:"UserController"})

     apiUrls = {
         "login": "/login"


Linted: 80 char rule

Linted: 80 char rule

--- server/lib/report-manager.coffee
+++ server/lib/report-manager.coffee
@@ -58,10 +58,12 @@
                         @_prepareBalancesData frequency, includedBankAccounts, \
                         (err, accounts) =>
                             if accounts.length > 0
-                                textContent = @_getTextContent operationsByAccount,\
-                                                               accounts, frequency
-                                htmlContent = @_getHtmlContent operationsByAccount,\
-                                                               accounts, frequency
+                                textContent = \
+                                @_getTextContent operationsByAccount,\
+                                                 accounts, frequency
+                                htmlContent = \
+                                @_getHtmlContent operationsByAccount,\
+                                                 accounts, frequency
                                 @_sendReport frequency, textContent, htmlContent
                 else
                     console.log "User hasn't enabled #{frequency} report."


Testing

Testing

--- widgets/cusi_progress_bars/cusi_progress_bars.coffee
+++ widgets/cusi_progress_bars/cusi_progress_bars.coffee
@@ -40,8 +40,8 @@

         elem = rowsContainer.find("."+normalizedItemName+" .inner-progress-bar")
         if elem.length
-          @animateProgressBarContent(elem[0], parseFloat(elem[0].style.width),
-                                    parseFloat(item.progress), item.value, 100)#1000)
+          @animateProgressBarContent(elem[0], parseFloat(elem[0].text), #parseFloat(elem[0].style.width),
+                                    parseFloat(item.progress), item.value, 100) #1000)
         ++counter

       # Remove any nodes that were not in the new data, these will be the rows


Updates paths for traverse specs

Updates paths for traverse specs


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -5,7 +5,7 @@
   equals animation.active(), 0
 ###

-asyncTest "Active Animation", ->
+asyncTest "Animation has active property", ->
   animation = Animation.fromPixieId 45

   milliseconds = 300


Added test for math.round

Added test for math.round

--- test/02-math.coffee
+++ test/02-math.coffee
@@ -23,3 +23,19 @@
       value = 33
       result = fk.math.fit value, 0, 0.25, 0, 1
       result.should.equal 1
+
+
+  describe '#round()', ->
+    it 'should work', ->
+      result = fk.math.round 0.123456789, 1
+      result.toString().should.equal "0.1"
+
+      result = fk.math.round 0.123456789, 3
+      result.toString().should.equal "0.123"
+
+      result = fk.math.round 0.1234, 4
+      result.toString().should.equal "0.1234"
+
+      # fails V8s number precision
+#      result = fk.math.round 0.1234, 5
+#      result.toString().should.equal "0.12345"


Account for range offsets that target the last child element of its container

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -3,6 +3,7 @@
     @element.setAttribute("contenteditable", "true")
     @element.setAttribute("autocorrect", "off")
     @element.setAttribute("spellcheck", "false")
+    @element.trixPosition = 0

   focus: ->
     @element.focus()
@@ -159,7 +160,13 @@
     if container.nodeType is Node.TEXT_NODE
       container.trixPosition + offset
     else
-      container.childNodes[offset]?.trixPosition ? offset
+      if container.hasChildNodes()
+        if container.childNodes.length is offset
+          container.lastChild.trixPosition + 1
+        else
+          container.childNodes[offset].trixPosition
+      else
+        container.trixPosition

   findContainerAndOffsetForPosition: (position) ->
     return [@element, 0] if position < 1
@@ -172,10 +179,17 @@
       node = walker.currentNode

     if node.nodeType is Node.TEXT_NODE
-      [node, position - node.trixPosition]
+      container = node
+      offset = position - node.trixPosition
     else
-      offset = [node.parentNode.childNodes...].indexOf(node)
-      [node.parentNode, offset]
+      container = node.parentNode
+      offset =
+        if node.nextSibling
+          [node.parentNode.childNodes...].indexOf(node)
+        else
+          node.parentNode.childNodes.length
+
+    [container, offset]

   isWithin = (ancestor, element) ->
     while element


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -60,5 +60,5 @@
   p1 = Collision.rayRectangle start, rect.bounds()

   equals p1.y, 0
-  equals p1.x,
+  equals p1.x, 20
 module()


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -60,7 +60,7 @@

   debugger

-  p1 = Collision.rayRectangle start, dire bounds
+  p1 = Collision.rayRectangle start, direction, bounds

   equals p1.y, 0
   equals p1.x, 20