Cleanup

Cleanup

--- lib/assets/javascripts/moe-legacy/browser-view.coffee
+++ lib/assets/javascripts/moe-legacy/browser-view.coffee
@@ -24,19 +24,15 @@
 #   don't pull an out-of-date page next time.  This is slower, and would require us
 #   to be careful about expiring the cache.
 ###
-
 window.BrowserView = (container) ->
   @container = container

-  ### The post that we currently want to display.  This will be either one of the
+  # The post that we currently want to display.  This will be either one of the
   # current html_preloads, or be the displayed_post_id.
-  ###
-
   @wanted_post_id = null
   @wanted_post_frame = null

-  ### The post that's currently actually being displayed. ###
-
+  # The post that's currently actually being displayed.
   @displayed_post_id = null
   @displayed_post_frame = null
   @current_ajax_request = null
@@ -46,12 +42,10 @@
   @img_box = @container.down('.image-box')
   @container.down '.image-canvas'

-  ### In Opera 10.63, the img.complete property is not reset to false after changing the
+  # In Opera 10.63, the img.complete property is not reset to false after changing the
   # src property.  Blits from images to the canvas silently fail, with nothing being
   # blitted and no exception raised.  This causes blank canvases to be displayed, because
   # we have no way of telling whether the image is blittable or if the blit succeeded.
-  ###
-
   if !Prototype.Browser.Opera
     @canvas = create_canvas_2d()
   if @canvas
@@ -59,8 +53,7 @@
     @img_box.appendChild @canvas
   @zoom_level = 0

-  ### True if the post UI is visible. ###
-
+  # True if the post UI is visible.
   @post_ui_visible = true
   @update_navigator = @update_navigator.bind(this)
   Event.on window, 'resize', @window_resize_event.bindAsEventListener(this)
@@ -72,15 +65,11 @@
   if TagCompletion
     TagCompletion.init()

-  ### Double-clicking the main image, or on nothing, toggles the thumb bar. ###
-
+  # Double-clicking the main image, or on nothing, toggles the thumb bar.
   @container.down('.image-container').on 'dblclick', '.image-container', ((event) ->
-
-    ### Watch out: Firefox fires dblclick events for all buttons, with the standard
+    # Watch out: Firefox fires dblclick events for all buttons, with the standard
     # button maps, but IE only fires it for left click and doesn't set button at
     # all, so event.isLeftClick won't work.
-    ###
-
     if event.button
       return
     event.stop()
@@ -88,8 +77,7 @@
     return
   ).bindAsEventListener(this)

-  ### Image controls: ###
-
+  # Image controls:
   document.on 'viewer:view-large-toggle', ((e) ->
     @toggle_view_large_image()
     return
@@ -104,11 +92,9 @@
   @container.down('.post-frames').on 'click', '.post-frame-link', ((e, item) ->
     e.stop()

-    ### Change the displayed post frame to the one that was clicked.  Since all post frames
+    # Change the displayed post frame to the one that was clicked.  Since all post frames
     # are usually displayed in the thumbnail view, set center_thumbs to true to recenter
     # on the thumb that was clicked, so it's clearer what's happening.
-    ###
-
     document.fire 'viewer:set-active-post',
       post_id: @displayed_post_id
       post_frame: item.post_frame
@@ -116,16 +102,12 @@
     return
   ).bind(this)

-  ### We'll receive this message from the thumbnail view when the overlay is
+  # We'll receive this message from the thumbnail view when the overlay is
   # visible on the bottom of the screen, to tell us how much space is covered up
   # by it.
-  ###
-
   @thumb_bar_height = 0
   document.on 'viewer:thumb-bar-changed', ((e) ->
-
-    ### Update the thumb bar height and rescale the image to fit the new area. ###
-
+    # Update the thumb bar height and rescale the image to fit the new area.
     @thumb_bar_height = e.memo.height
     @update_image_window_size()
     @set_post_ui e.memo.shown
@@ -149,8 +131,7 @@
     }.bindAsEventListener(this));
   ###

-  ### Hide member-only and moderator-only controls: ###
-
+  # Hide member-only and moderator-only controls:
   $(document.body).pickClassName 'is-member', 'not-member', User.is_member_or_higher()
   $(document.body).pickClassName 'is-moderator', 'not-moderator', User.is_mod_or_higher()
   tag_span = @container.down('.post-tags')
@@ -160,10 +141,8 @@
     return
   ).bind(this)

-  ### These two links do the same thing, but one is shown to approve a pending post
+  # These two links do the same thing, but one is shown to approve a pending post
   # and the other is shown to unflag a flagged post, so they prompt differently.
-  ###
-
   @container.down('.post-approve').on 'click', ((e) ->
     e.stop()
     if !confirm('Approve this post?')
@@ -231,7 +210,7 @@
       return
     post_id = @displayed_post_id
     post = Post.posts.get(post_id)
-    if post == null or post == undefined
+    if !post?
       return
     Post.reparent_post post_id, post.parent_id, false
     return
@@ -247,8 +226,7 @@
     return
   ).bind(this)

-  ### Post editing: ###
-
+  # Post editing:
   post_edit = @container.down('.post-edit')
   post_edit.down('FORM').on 'submit', ((e) ->
     e.stop()
@@ -282,11 +260,9 @@
   new TagCompletionBox(post_edit.down('.edit-tags'))
   @container.down('.post-edit').on 'keydown', ((e) ->

-    ### Don't e.stop() KEY_ESC, so we fall through and let handle_keypress unfocus the
+    # Don't e.stop() KEY_ESC, so we fall through and let handle_keypress unfocus the
     # form entry, if any.  Otherwise, Chrome gets confused and leaves the focus on the
     # hidden input, where it'll steal keystrokes.
-    ###
-
     if e.keyCode == Event.KEY_ESC
       @edit_show false
     else if e.keyCode == Event.KEY_RETURN
@@ -295,20 +271,17 @@
     return
   ).bindAsEventListener(this)

-  ### When the edit-post hotkey is pressed (E), force the post UI open and show editing. ###
-
+  # When the edit-post hotkey is pressed (E), force the post UI open and show editing.
   document.on 'viewer:edit-post', ((e) ->
     document.fire 'viewer:set-thumb-bar', set: true
     @edit_show true
     return
   ).bindAsEventListener(this)

-  ### When the post that's currently being displayed is updated by an API call, update
+  # When the post that's currently being displayed is updated by an API call, update
   # the displayed info.
-  ###
-
   document.on 'posts:update', ((e) ->
-    if e.memo.post_ids.get(@displayed_post_id) == null or e.memo.post_ids.get(@displayed_post_id) == undefined
+    if !e.memo.post_ids.get(@displayed_post_id)?
       return
     @set_post_info()
     return
@@ -340,10 +313,8 @@
     @create_voting_popup()
     @image_swipe = new SwipeHandler(@container.down('.image-container'))

-  ### Create the frame editor.  This must be created before image_dragger, since it takes priority
+  # Create the frame editor.  This must be created before image_dragger, since it takes priority
   # for drags.
-  ###
-
   @container.down('.edit-frames-button').on 'click', ((e) ->
     e.stop()
     @show_frame_editor()
@@ -354,17 +325,15 @@
     return
   ).bind(this))

-  ### If we're using dragging as a swipe gesture (see SwipeHandler), don't use it for
+  # If we're using dragging as a swipe gesture (see SwipeHandler), don't use it for
   # dragging too.
-  ###
-
-  if @image_swipe == null or @image_swipe == undefined
+  if !@image_swipe?
     @image_dragger = new WindowDragElementAbsolute(@img_box, @update_navigator)
   return

 BrowserView::create_voting_popup = ->

-  ### Create the low-level voting widget. ###
+  # Create the low-level voting widget.

   popup_vote_widget_container = @container.down('.vote-popup-container')
   popup_vote_widget_container.show()
@@ -372,22 +341,17 @@
   @popup_vote_widget.initShortcut()
   flash = @container.down('.vote-popup-flash')

-  ### vote-popup-expand is the part that's always present and is clicked to display the
+  # vote-popup-expand is the part that's always present and is clicked to display the
   # voting popup.  Create a dragger on it, and pass the position down to the voting
   # popup as we drag around.
-  ###
-
   popup_expand = @container.down('.vote-popup-expand')
   popup_expand.show()
   last_dragged_over = null
   @popup_vote_dragger = new DragElement(popup_expand,
     ondown: ((drag) ->
-
-      ### Stop the touchdown/mousedown events, so this drag takes priority over any
+      # Stop the touchdown/mousedown events, so this drag takes priority over any
       # others.  In particular, we don't want this.image_swipe to also catch this
       # as a drag.
-      ###
-
       drag.latest_event.stop()
       flash.hide()
       flash.removeClassName 'flash-star'
@@ -397,24 +361,18 @@
       return
     ).bind(this)
     onup: ((drag) ->
-
-      ### If we're cancelling the drag, don't activate the vote, if any. ###
-
+      # If we're cancelling the drag, don't activate the vote, if any.
       if drag.cancelling
         debug 'cancelling drag'
         last_dragged_over = null

-      ### Call even if star_container is null or not a star, so we clear any mouseover. ###
-
+      # Call even if star_container is null or not a star, so we clear any mouseover.
       @popup_vote_widget.set_mouseover last_dragged_over
       star = @popup_vote_widget.activate_item(last_dragged_over)

-      ### If a vote was made, flash the vote star. ###
-
-      if star != null and star != undefined
-
-        ### Set the star-# class to color the star. ###
-
+      # If a vote was made, flash the vote star.
+      if star?
+        # Set the star-# class to color the star.
         i = 0
         while i < 4
           flash.removeClassName 'star-' + i
@@ -422,8 +380,7 @@
         flash.addClassName 'star-' + star
         flash.show()

-        ### Center the element on the screen. ###
-
+        # Center the element on the screen.
         offset = @image_window_size
         flash_x = offset.width / 2 - (flash.offsetWidth / 2)
         flash_y = offset.height / 2 - (flash.offsetHeight / 2)
@@ -443,39 +400,30 @@
   return

 BrowserView::set_post_ui = (visible) ->
-
-  ### Disable the post UI by default on touchscreens; we don't have an interface
+  # Disable the post UI by default on touchscreens; we don't have an interface
   # to toggle it.
-  ###
-
   if Prototype.BrowserFeatures.Touchscreen and window.screen.availWidth < 1024
     visible = false

-  ### If we don't have a post displayed, always hide the post UI even if it's currently
+  # If we don't have a post displayed, always hide the post UI even if it's currently
   # shown.
-  ###
-
-  @container.down('.post-info').show visible and @displayed_post_id != null and @displayed_post_id != undefined
+  @container.down('.post-info').show visible and @displayed_post_id?
   if visible == @post_ui_visible
     return
   @post_ui_visible = visible
   if @navigator
     @navigator.set_autohide !visible

-  ### If we're hiding the post UI, cancel the post editor if it's open. ###
-
+  # If we're hiding the post UI, cancel the post editor if it's open.
   if !@post_ui_visible
     @edit_show false
   return

 BrowserView::image_loaded_event = (event) ->
-
-  ### Record that the image is completely available, so it can be blitted to the canvas.
+  # Record that the image is completely available, so it can be blitted to the canvas.
   # This is different than img.complete, which is true if the image has completed downloading
   # but hasn't yet been decoded, so isn't yet completely available.  This generally happens
   # if we query img.completed quickly after setting img.src and the image data is cached.
-  ###
-
   @img.fully_loaded = true
   document.fire 'viewer:displayed-image-loaded',
     post_id: @displayed_post_id
@@ -483,23 +431,18 @@
   @update_canvas()
   return

-### Return true if last_preload_request includes [post_id, post_frame]. ###
-
+# Return true if last_preload_request includes [post_id, post_frame].
 BrowserView::post_frame_list_includes = (post_id_list, post_id, post_frame) ->
   found_preload = post_id_list.find((post) ->
     post[0] == post_id and post[1] == post_frame
   )
-  found_preload != null and found_preload != undefined
-
-### Begin preloading the HTML and images for the given post IDs. ###
-
+  found_preload?
+
+# Begin preloading the HTML and images for the given post IDs.
 BrowserView::preload = (post_ids) ->
-
-  ### We're being asked to preload post_ids.  Only do this if it seems to make sense: if
+  # We're being asked to preload post_ids.  Only do this if it seems to make sense: if
   # the user is actually traversing posts that are being preloaded.  Look at the previous
   # call to preload().  If it didn't include the current post, then skip the preload.
-  ###
-
   last_preload_request = @last_preload_request
   @last_preload_request = post_ids
   if !@post_frame_list_includes(last_preload_request, @wanted_post_id, @wanted_post_frame)
@@ -521,12 +464,10 @@
       new_preload_container.preload post.sample_url
     ++i

-  ### If we already were preloading images, we created the new preloads before
+  # If we already were preloading images, we created the new preloads before
   # deleting the old ones.  That way, any images that are still being preloaded
   # won't be deleted and recreated, possibly causing the download to be interrupted
   # and resumed.
-  ###
-
   if @preload_container
     @preload_container.destroy()
   @preload_container = new_preload_container
@@ -536,7 +477,7 @@
   debug 'load needed'
   # If we already have a request in flight, don't start another; wait for the
   # first to finish.
-  if @current_ajax_request != null and @current_ajax_request != undefined
+  if @current_ajax_request?
     return
   new (Ajax.Request)('/post.json',
     parameters:
@@ -555,10 +496,8 @@
       if @current_ajax_request != resp.request
         return

-      ### If no posts were returned, then the post ID we're looking up doesn't exist;
+      # If no posts were returned, then the post ID we're looking up doesn't exist;
       # treat this as a failure.
-      ###
-
       resp = resp.responseJSON
       @success = resp.posts.length > 0
       if !@success
@@ -571,23 +510,18 @@
       if @current_ajax_request == resp.request
         @current_ajax_request = null

-      ### If the request failed and we were requesting wanted_post_id, don't keep trying. ###
+      # If the request failed and we were requesting wanted_post_id, don't keep trying.

       success = resp.request.success() and @success
       if !success and post_id == @wanted_post_id
-
-        ### As a special case, if the post we requested doesn't exist and we aren't displaying
+        # As a special case, if the post we requested doesn't exist and we aren't displaying
         # anything at all, force the thumb bar open so we don't show nothing at all.
-        ###
-
-        if @displayed_post_id == null or @displayed_post_id == undefined
+        if !@displayed_post_id?
           document.fire 'viewer:set-thumb-bar', set: true
         return

-      ### This will either load the post we just finished, or request data for the
+      # This will either load the post we just finished, or request data for the
       # one we want.
-      ###
-
       @set_post @wanted_post_id, @wanted_post_frame
       return
     ).bind(this)
@@ -600,29 +534,24 @@
 BrowserView::set_viewing_larger_version = (b) ->
   @viewing_larger_version = b
   post = Post.posts.get(@displayed_post_id)
-  can_zoom = post != null and post != undefined and post.jpeg_url != post.sample_url
+  can_zoom = post? and post.jpeg_url != post.sample_url
   @container.down('.zoom-icon-none').show !can_zoom
   @container.down('.zoom-icon-in').show can_zoom and !@viewing_larger_version
   @container.down('.zoom-icon-out').show can_zoom and @viewing_larger_version

-  ### When we're on the regular version and we're on a touchscreen, disable drag
+  # When we're on the regular version and we're on a touchscreen, disable drag
   # scrolling so we can use it to switch images instead.
-  ###
-
   if Prototype.BrowserFeatures.Touchscreen and @image_dragger
     @image_dragger.set_disabled !b

-  ### Only allow dragging to create new frames when not viewing the large version,
+  # Only allow dragging to create new frames when not viewing the large version,
   # since we need to be able to drag the image.
-  ###
-
   if @frame_editor
     @frame_editor.set_drag_to_create !b
     @frame_editor.set_show_corner_drag !b
   return

 BrowserView::set_main_image = (post, post_frame) ->
-
   ###
   # Clear the previous post, if any.  Don't keep the old IMG around; create a new one, or
   # we may trigger long-standing memory leaks in WebKit, eg.:
@@ -631,14 +560,13 @@
   # This also helps us avoid briefly displaying the old image with the new dimensions, which
   # can otherwise take some hoop jumping to prevent.
   ###
-
-  if @img != null and @img != undefined
+  if @img?
     @img.stopObserving()
     @img.parentNode.removeChild @img
     @image_pool.release @img
     @img = null

-  ### If this post is blacklisted, show a message instead of displaying it. ###
+  # If this post is blacklisted, show a message instead of displaying it.

   hide_post = Post.is_blacklisted(post.id) and post.id != @blacklist_override_post_id
   @container.down('.blacklisted-message').show hide_post
@@ -657,7 +585,6 @@
   # event.  Work around this by setting the image to pointer-events: none, so clicks on
   # the image will actually be sent to the containing box directly.
   ###
-
   @img.setStyle pointerEvents: 'none'
   @img.on 'load', @image_loaded_event.bindAsEventListener(this)
   @img.fully_loaded = false
@@ -678,11 +605,8 @@
     @img_box.original_height = post.sample_height
     @img_box.show()
   else
-
-    ### Having no sample URL is an edge case, usually from deleted posts.  Keep the number
+    # Having no sample URL is an edge case, usually from deleted posts.  Keep the number
     # of code paths smaller by creating the IMG anyway, but not showing it.
-    ###
-
     @img_box.hide()
   @container.down('.image-box').appendChild @img
   if @viewing_larger_version
@@ -705,13 +629,11 @@
 # no_hash_change should also be set when loading a state as a result of hashchange,
 # for similar reasons.
 ###
-
 BrowserView::set_post = (post_id, post_frame, lazy, no_hash_change, replace_history) ->
-  if post_id == null or post_id == undefined
+  if !post_id?
     throw 'post_id must not be null'

-  ### If there was a lazy load pending, cancel it. ###
-
+  # If there was a lazy load pending, cancel it.
   @cancel_lazily_load()
   @wanted_post_id = post_id
   @wanted_post_frame = post_frame
@@ -720,10 +642,8 @@
   if post_id == @displayed_post_id and post_frame == @displayed_post_frame
     return

-  ### If a lazy load was requested and we're not yet loading the image for this post,
+  # If a lazy load was requested and we're not yet loading the image for this post,
   # delay loading.
-  ###
-
   is_cached = @last_preload_request_active and @post_frame_list_includes(@last_preload_request, post_id, post_frame)
   if lazy and !is_cached
     @lazy_load_timer = window.setTimeout((->
@@ -734,22 +654,19 @@
     return
   @hide_frame_editor()
   post = Post.posts.get(post_id)
-  if post == null or post == undefined
-
-    ### The post we've been asked to display isn't loaded.  Request a load and come back. ###
-
-    if @displayed_post_id == null or @displayed_post_id == undefined
+  if !post?
+    # The post we've been asked to display isn't loaded.  Request a load and come back.
+    if !@displayed_post_id?
       @container.down('.post-info').hide()
     @load_post_id_data post_id
     return
-  if post_frame == null or post_frame == undefined
+  if !post_frame?
     # If post_frame is unspecified and we have a frame, display the first.
     post_frame = @get_default_post_frame(post_id)
     # We know what frame we actually want to display now, so update wanted_post_frame.
     @wanted_post_frame = post_frame

-  ### If post_frame doesn't exist, just display the main post. ###
-
+  # If post_frame doesn't exist, just display the main post.
   if post_frame != -1 and post.frames.length <= post_frame
     post_frame = -1
   @displayed_post_id = post_id
@@ -776,35 +693,33 @@
     post_frame: post_frame
   @set_post_info()

-  ### Hide the editor when changing posts. ###
-
+  # Hide the editor when changing posts.
   @edit_show false
   return

-### Return the frame spec for the hash, eg. "-0".
+###
+# Return the frame spec for the hash, eg. "-0".
 #
 # If the post has no frames, then just omit the frame spec.  If the post has any frames,
 # then return the frame number or "-F" for the full image.
 ###
-
 BrowserView::post_frame_hash = (post, post_frame) ->
   if post.frames.length == 0
     return ''
   '-' + (if post_frame == -1 then 'F' else post_frame)

-### Return the default frame to display for the given post.  If the post isn't loaded,
+###
+# Return the default frame to display for the given post.  If the post isn't loaded,
 # we don't know which frame we'll display and null will be returned.  This corresponds
 # to a hash of #1234, where no frame is specified (eg. #1234-F, #1234-0).
 ###
-
 BrowserView::get_default_post_frame = (post_id) ->
   post = Post.posts.get(post_id)
-  if post == null or post == undefined
+  if !post?
     return null
   if post.frames.length > 0 then 0 else -1

 BrowserView::get_post_frame_hash = (post, post_frame) ->
-
   ###
   # Omitting the frame in the hash selects the default frame: the first frame if any,
   # otherwise the full image.  If we're setting the hash to a post_frame which would be
@@ -815,15 +730,13 @@
   # This helps normalize the hash.  Otherwise, loading /#1234 will update the hash to
   # /#1234-in set_post, causing an unwanted history entry.
   ###
-
   default_frame = if post.frames.length > 0 then 0 else -1
   if post_frame == default_frame
     null
   else
     post_frame

-### Set the post info box for the currently displayed post. ###
-
+# Set the post info box for the currently displayed post.
 BrowserView::set_post_info = ->
   post = Post.posts.get(@displayed_post_id)
   if !post
@@ -833,8 +746,7 @@
   @container.down('.posted-by').show()
   @container.down('.posted-at').setTextContent time_ago_in_words(new Date(post.created_at * 1000))

-  ### Fill in the pool list. ###
-
+  # Fill in the pool list.
   pool_info = @container.down('.pool-info')
   while pool_info.firstChild
     pool_info.removeChild pool_info.firstChild
@@ -859,7 +771,7 @@
       pool_info.appendChild div
       return
     ).bind(this)
-  if post.creator_id != null and post.creator_id != undefined
+  if post.creator_id?
     @container.down('.posted-by').down('A').href = '/user/show/' + post.creator_id
     @container.down('.posted-by').down('A').setTextContent post.author
   else
@@ -888,19 +800,16 @@
         text = text.substr(0, 20) + '...'
       url = post.source
     source_box = @container.down('.post-source')
-    source_box.down('A').show url != null and url != undefined
-    source_box.down('SPAN').show url == null or url == undefined
+    source_box.down('A').show url?
+    source_box.down('SPAN').show !url?
     if url
       source_box.down('A').href = url
       source_box.down('A').setTextContent text
     else
       source_box.down('SPAN').setTextContent text
   if post.frames.length > 0
-
-    ### Hide this with a class rather than by changing display, so show_frame_editor
+    # Hide this with a class rather than by changing display, so show_frame_editor
     # and hide_frame_editor can hide and unhide this separately.
-    ###
-
     @container.down('.post-frames').removeClassName 'no-frames'
     frame_list = @container.down('.post-frame-list')
     while frame_list.firstChild
@@ -936,12 +845,10 @@

   has_sample = post.sample_url != post.file_url
   has_jpeg = post.jpeg_url != post.file_url
-  has_image = post.file_url != null and post.file_url != undefined and !has_sample
-
-  ### Hide the whole download-links box if there are no downloads available, usually
+  has_image = post.file_url? and !has_sample
+
+  # Hide the whole download-links box if there are no downloads available, usually
   # because of a deleted post.
-  ###
-
   @container.down('.download-links').show has_image or has_sample or has_jpeg
   @container.down('.download-image').show has_image
   if has_image
@@ -958,13 +865,11 @@
     png_desc = number_to_human_size(post.file_size) + ' ' + file_extension(post.file_url.toUpperCase())
     @container.down('.download-png-desc').setTextContent png_desc

-  ### For links that are handled by click events, try to set the href so that copying the
+  # For links that are handled by click events, try to set the href so that copying the
   # link will give a similar effect.  For example, clicking parent-post will call set_post
   # to display it, and the href links to /post/browse#12345.
-  ###
-
   parent_post = @container.down('.parent-post')
-  parent_post.show post.parent_id != null and post.parent_id != undefined
+  parent_post.show post.parent_id?
   if post.parent_id
     parent_post.down('A').href = '/post/browse#' + post.parent_id
   child_posts = @container.down('.child-posts')
@@ -972,8 +877,7 @@
   if post.has_children
     child_posts.down('A').href = '/post/browse#/parent:' + post.id

-  ### Create the tag links. ###
-
+  # Create the tag links.
   tag_span = @container.down('.post-tags')
   first = true
   while tag_span.firstChild
@@ -992,12 +896,10 @@
       href: '/post/browse#/' + tag
       class: 'post-tag tag-type-' + type)

-    ### Break tags with <wbr>, so long tags can be wrapped. ###
-
+    # Break tags with <wbr>, so long tags can be wrapped.
     a.html a.html().replace(/_/g, '_<wbr>')

-    ### convert back to something Prototype or whatever can understand ###
-
+    # convert back to something Prototype or whatever can understand
     a = a[0]
     a.tag_name = tag
     span.appendChild a
@@ -1012,15 +914,14 @@
   flagged.show post.status == 'flagged'
   if post.status == 'flagged' and post.flag_detail
     byEl = flagged.down('.by')
-    flagged.down('.flagged-by-box').show post.flag_detail.user_id != null and post.flag_detail.user_id != undefined
-    if post.flag_detail.user_id != null and post.flag_detail.user_id != undefined
+    flagged.down('.flagged-by-box').show post.flag_detail.user_id?
+    if post.flag_detail.user_id?
       byEl.setTextContent post.flag_detail.flagged_by
       byEl.href = '/user/show/' + post.flag_detail.user_id
     reason = flagged.down('.reason')
     reason.setTextContent post.flag_detail.reason

-  ### Moderators can unflag images, and the person who flags an image can unflag it himself. ###
-
+  # Moderators can unflag images, and the person who flags an image can unflag it himself.
   is_flagger = post.flag_detail and post.flag_detail.user_id == User.get_current_user_id()
   can_unflag = flagged and (User.is_mod_or_higher() or is_flagger)
   flagged.down('.post-unflag').show can_unflag
@@ -1033,7 +934,7 @@
   deleted.show post.status == 'deleted'
   if post.status == 'deleted'
     by_container = deleted.down('.by-container')
-    by_container.show post.flag_detail.flagged_by != null and post.flag_detail.flagged_by != undefined
+    by_container.show post.flag_detail.flagged_by?
     byEl = by_container.down('.by')
     byEl.setTextContent post.flag_detail.flagged_by
     byEl.href = '/user/show/' + post.flag_detail.user_id
@@ -1054,17 +955,13 @@
   @container.down('.post-tags-box').show !shown
   @container.down('.post-edit').show shown
   if !shown
-
-    ### Revert all changes. ###
-
+    # Revert all changes.
     @frame_editor.discard()
     return
   @select_edit_box '.post-edit-main'

-  ### This returns [tag, tag type].  We only want the tag; we call this so we sort the
+  # This returns [tag, tag type].  We only want the tag; we call this so we sort the
   # tags consistently.
-  ###
-
   tags_by_type = Post.get_post_tags_with_type(post)
   tags = tags_by_type.pluck(0)
   tags = tags.join(' ') + ' '
@@ -1082,8 +979,7 @@
   @container.down('.edit-tags').focus()
   return

-### Set the size of the tag edit area to the size of its contents. ###
-
+# Set the size of the tag edit area to the size of its contents.
 BrowserView::edit_post_area_changed = ->
   post_edit = @container.down('.post-edit')
   element = post_edit.down('.edit-tags')
@@ -1094,16 +990,11 @@
     source = null
     parent_id = null
     element.value.split(' ').each ((tag) ->
-      m = undefined
-
-      ### This mimics what the server side does; it does prevent metatags from using
+      # This mimics what the server side does; it does prevent metatags from using
       # uppercase in source: metatags.
-      ###
-
       tag = tag.toLowerCase()

-      ### rating:q or just q: ###
-
+      # rating:q or just q:
       m = tag.match(/^(rating:)?([qse])$/)
       if m
         rating = m[2]
@@ -1127,16 +1018,14 @@
   save_completed = (->
     notice 'Post saved'

-    ### If we're still showing the post we saved, hide the edit area. ###
-
+    # If we're still showing the post we saved, hide the edit area.
     if @displayed_post_id == post_id
       @edit_show false
     return
   ).bind(this)
   post_id = @displayed_post_id

-  ### If we're in the frame editor, save it.  Don't save the hidden main editor. ###
-
+  # If we're in the frame editor, save it.  Don't save the hidden main editor.
   if @frame_editor
     if @frame_editor.is_opened()
       @frame_editor.save save_completed
@@ -1144,12 +1033,10 @@
   edit_tags = @container.down('.edit-tags')
   tags = edit_tags.value

-  ### Opera doesn't blur the field automatically, even when we hide it later. ###
-
+  # Opera doesn't blur the field automatically, even when we hide it later.
   edit_tags.blur()

-  ### Find which rating is selected. ###
-
+  # Find which rating is selected.
   rating_class = new Hash(
     s: '.edit-safe'
     q: '.edit-questionable'
@@ -1161,10 +1048,8 @@
     return
   ).bind(this)

-  ### update_batch will give us updates for any related posts, as well as the one we're
+  # update_batch will give us updates for any related posts, as well as the one we're
   # updating.
-  ###
-
   Post.update_batch [ {
     id: post_id
     tags: @container.down('.edit-tags').value
@@ -1185,42 +1070,33 @@

 BrowserView::toggle_view_large_image = ->
   post = Post.posts.get(@displayed_post_id)
-  if post == null or post == undefined
-    return
-  if @img == null or @img == undefined
+  if !post?
+    return
+  if !@img?
     return
   if post.jpeg_url == post.sample_url
-
-    ### There's no larger version to display. ###
-
-    return
-
-  ### Toggle between the sample and JPEG version. ###
-
+    # There's no larger version to display.
+    return
+
+  # Toggle between the sample and JPEG version.
   @set_viewing_larger_version !@viewing_larger_version
   @set_main_image post
   # XXX frame
   return

-### this.image_window_size is the size of the area where the image is visible. ###
-
+# this.image_window_size is the size of the area where the image is visible.
 BrowserView::update_image_window_size = ->
   @image_window_size = getWindowSize()

-  ### If the thumb bar is shown, exclude it from the window height and fit the image
+  # If the thumb bar is shown, exclude it from the window height and fit the image
   # in the remainder.  Since the bar is at the bottom, we don't need to do anything to
   # adjust the top.
-  ###
-
   @image_window_size.height -= @thumb_bar_height
+  # clamp to 0 if there's no space
   @image_window_size.height = Math.max(@image_window_size.height, 0)

-  ### clamp to 0 if there's no space ###
-
-  ### When the window size changes, update the navigator since the cursor will resize to
+  # When the window size changes, update the navigator since the cursor will resize to
   # match.
-  ###
-
   @update_navigator()
   return

@@ -1237,9 +1113,7 @@
   window_size = @image_window_size
   ratio = 1.0
   if !@viewing_larger_version
-
-    ### Zoom the image to fit the viewport. ###
-
+    # Zoom the image to fit the viewport.
     ratio = window_size.width / original_width
     if original_height * ratio > window_size.height
       ratio = window_size.height / original_height
@@ -1252,38 +1126,31 @@
   if @frame_editor
     @frame_editor.set_image_dimensions @displayed_image_width, @displayed_image_height

-  ### If we're resizing and showing the full-size image, don't snap the position
+  # If we're resizing and showing the full-size image, don't snap the position
   # back to the default.
-  ###
-
   if resizing and @viewing_larger_version
     return
   x = 0.5
   y = 0.5
   if @viewing_larger_version
-
-    ### Align the image to the top of the screen. ###
-
+    # Align the image to the top of the screen.
     y = @image_window_size.height / 2
     y /= @displayed_image_height
   @center_image_on x, y
   return

-### x and y are [0,1]. ###
-
+# x and y are [0,1].
 BrowserView::update_navigator = ->
   if !@navigator
     return
   if !@img
     return

-  ### The coordinates of the image located at the top-left corner of the window: ###
-
+  # The coordinates of the image located at the top-left corner of the window:
   scroll_x = -@img_box.offsetLeft
   scroll_y = -@img_box.offsetTop

-  ### The coordinates at the center of the window: ###
-
+  # The coordinates at the center of the window:
   x = scroll_x + @image_window_size.width / 2
   y = scroll_y + @image_window_size.height / 2
   percent_x = x / @displayed_image_width
@@ -1316,7 +1183,6 @@
 #   as necessary to paint, but that's a lot more work.
 # - Canvas won't blit partially-loaded images, so we do nothing until the image is complete.
 ###
-
 BrowserView::update_canvas = ->
   if !@img.fully_loaded
     debug 'image incomplete; can\'t render to canvas'
@@ -1324,10 +1190,8 @@
   if !@canvas
     return

-  ### If the contents havn't changed, skip the blit.  This happens frequently when resizing
+  # If the contents havn't changed, skip the blit.  This happens frequently when resizing
   # the window when not fitting the image to the screen.
-  ###
-
   if @canvas.rendered_url == @img.src and @canvas.width == @displayed_image_width and @canvas.height == @displayed_image_height
     # debug(this.canvas.rendered_url + ", " + this.canvas.width + ", " + this.canvas.height)
     # debug("Skipping canvas blit");
@@ -1355,13 +1219,13 @@
   return

 BrowserView::cancel_lazily_load = ->
-  if @lazy_load_timer == null or @lazy_load_timer == undefined
+  if !@lazy_load_timer?
     return
   window.clearTimeout @lazy_load_timer
   @lazy_load_timer = null
   return

-### Update the window title when the display changes. ###
+# Update the window title when the display changes.

 window.WindowTitleHandler = ->
   @searched_tags = ''
@@ -1388,7 +1252,6 @@
   return

 WindowTitleHandler::update = ->
-  title = undefined
   post = Post.posts.get(@post_id)
   if @pool
     title = @pool.name.replace(/_/g, ' ')
@@ -1409,7 +1272,7 @@
 BrowserView::parent_post_click_event = (event) ->
   event.stop()
   post = Post.posts.get(@displayed_post_id)
-  if post == null or post == undefined or post.parent_id == null or post.parent_id == undefined
+  if !post? || !post.parent_id?
     return
   @set_post post.parent_id
   return
@@ -1417,10 +1280,8 @@
 BrowserView::child_posts_click_event = (event) ->
   event.stop()

-  ### Search for this post's children.  Set the results mode to center-on-current, so we
+  # Search for this post's children.  Set the results mode to center-on-current, so we
   # focus on the current item.
-  ###
-
   document.fire 'viewer:perform-search',
     tags: 'parent:' + @displayed_post_id
     results_mode: 'center-on-current'
@@ -1436,8 +1297,7 @@
 BrowserView::show_frame_editor = ->
   @select_edit_box '.frame-editor'

-  ### If we're displaying a frame and not the whole image, switch to the main image. ###
-
+  # If we're displaying a frame and not the whole image, switch to the main image.
   post_frame = null
   if @displayed_post_frame != -1
     post_frame = @displayed_post_frame
@@ -1447,11 +1307,9 @@
   @frame_editor.open @displayed_post_id
   @container.down('.post-frames').hide()

-  ### If we were on a frame when opened, focus the frame we were on.  Otherwise,
+  # If we were on a frame when opened, focus the frame we were on.  Otherwise,
   # leave it on the default.
-  ###
-
-  if post_frame != null and post_frame != undefined
+  if post_frame?
     @frame_editor.focus post_frame
   return

@@ -1518,24 +1376,18 @@

 Navigator::ondrag = (e) ->
   coords = @get_normalized_coords(e.x, e.y)
-  if e.latest_event.shiftKey != (@shift_lock_anchor != null and @shift_lock_anchor != undefined)
-
-    ### The shift key has been pressed or released. ###
-
+  if e.latest_event.shiftKey != @shift_lock_anchor?
+
+    # The shift key has been pressed or released.
     if e.latest_event.shiftKey
-
-      ### The shift key was just pressed.  Remember the position we were at when it was
+      # The shift key was just pressed.  Remember the position we were at when it was
       # pressed.
-      ###
-
       @shift_lock_anchor = [
         coords[0]
         coords[1]
       ]
     else
-
-      ### The shift key was released. ###
-
+      # The shift key was released.
       @shift_lock_anchor = null
       @locked_to_x = null
   @center_on_position coords
@@ -1543,8 +1395,7 @@

 Navigator::image_position_changed = (percent_x, percent_y, height_percent, width_percent) ->

-  ### When the image is moved or the visible area is resized, update the cursor rectangle. ###
-
+  # When the image is moved or the visible area is resized, update the cursor rectangle.
   cursor = @container.down('.navigator-cursor')
   cursor.setStyle
     top: @img.height * (percent_y - (height_percent / 2)) + 'px'
@@ -1564,27 +1415,22 @@
     y
   ]

-### x and y are absolute window coordinates. ###
-
+# x and y are absolute window coordinates.
 Navigator::center_on_position = (coords) ->
   if @shift_lock_anchor
-    if @locked_to_x == null or @locked_to_x == undefined
-
-      ### Only change the coordinate with the greater delta. ###
-
+    if !@locked_to_x?
+
+      # Only change the coordinate with the greater delta.
       change_x = Math.abs(coords[0] - (@shift_lock_anchor[0]))
       change_y = Math.abs(coords[1] - (@shift_lock_anchor[1]))

-      ### Only lock to moving vertically or horizontally after we've moved a small distance
+      # Only lock to moving vertically or horizontally after we've moved a small distance
       # from where shift was pressed.
-      ###
-
       if change_x > 0.1 or change_y > 0.1
         @locked_to_x = change_x > change_y

-    ### If we've chosen an axis to lock to, apply it. ###
-
-    if @locked_to_x != null and @locked_to_x != undefined
+    # If we've chosen an axis to lock to, apply it.
+    if @locked_to_x?
       if @locked_to_x
         coords[1] = @shift_lock_anchor[1]
       else


Only connect if not connected, and disconnect if connected

--- src/lib/InternalSocket.coffee
+++ src/lib/InternalSocket.coffee
@@ -236,9 +236,9 @@

     # Emit the legacy event
     legacyEvent = @ipToLegacy ip
-    @emitEvent 'connect' if ip.type is 'data' and @braceCount is 0
+    @emitEvent 'connect' if ip.type is 'data' and @braceCount is 0 and not @connected
     @emitEvent legacyEvent.event, legacyEvent.payload
-    @emitEvent 'disconnect' if ip.type is 'data' and @braceCount is 0
+    @emitEvent 'disconnect' if ip.type is 'data' and @braceCount is 0 and @connected

 exports.InternalSocket = InternalSocket


add img preview and webp gulptask

add img preview and webp gulptask

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -27,6 +27,10 @@
 clean = require "gulp-clean"
 rev = require 'gulp-rev'
 rev_replace = require 'gulp-rev-replace'
+webp = require 'gulp-webp'
+size = require 'gulp-size'
+rename = require 'gulp-rename'
+imagemin = require 'gulp-imagemin'

 # =================================================================
 # high level tasks
@@ -43,7 +47,7 @@

 gulp.task "preview", (cb)->
   return runSequence  ['build:clean', 'js:clean'],
-                      ['css:build','js:build'],
+                      ['css:build','js:build','img:make:previews'],
                       'build_wintersmith',
                       'css:clean',
                       cb
@@ -191,6 +195,35 @@
     .pipe(image_min(options))
     .pipe(gulp.dest('./'))

+# =================================================================
+# lazyload & webp tasks
+# =================================================================
+
+gulp.task 'img:make:previews', ->
+  options = {
+    resize: [20,20],
+    quality: 85,
+    progressive: true,
+    compressionLevel: 6,
+    sequentialRead: true,
+    trellisQuantisation: false
+  }
+
+  return gulp.src('build/media/images/**/*.{jpg,png}',{base: './'})
+    .pipe(plumber())
+    .pipe(image_min(options))
+    .pipe rename
+      suffix: '_preview'
+    .pipe(gulp.dest('./'))
+
+gulp.task 'webp:make', ->
+  return gulp.src('contents/media/images/**/*.{jpg,png}',{base: './'})
+    .pipe plumber()
+    .pipe size()
+    .pipe webp
+      quality: 80
+    .pipe size()
+    .pipe gulp.dest('./')

 # =================================================================
 # site compile tasks


Add newline after gulp file

Add newline after gulp file

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -77,4 +77,4 @@
   gulp.watch src.stylus, ['stylus']
   gulp.watch src.jade, ['jade']

-gulp.task 'default', ['build', 'docs', 'watch']+gulp.task 'default', ['build', 'docs', 'watch']


we dont min the site.css

we dont min the site.css

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -219,7 +219,6 @@
         else
           console.log "failed to render #{file.path}"
     .pipe replace /\/css\/cyclops\.css/i, "https://assets.ctl.io/cyclops/#{pkg.version}/css/cyclops.min.css"
-    .pipe replace /\/css\/site\.css/i, "https://assets.ctl.io/cyclops/#{pkg.version}/css/site.min.css"
     .pipe replace /\/scripts\/cyclops\.js/i, "https://assets.ctl.io/cyclops/#{pkg.version}/scripts/cyclops.min.js"
     .pipe gulp.dest "./dist/#{pkg.version}/"


Gulp compass subdirectories of `sass/`

Gulp compass subdirectories of `sass/`

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -45,7 +45,7 @@

 gulp.task 'compass', ->
   for path in ['', 'docs/welcome/', 'docs/welcome/examples/social-sharing/']
-    gulp.src("./#{ path }sass/*")
+    gulp.src("./#{ path }sass/**/*.sass")
       .pipe(compass(
         sass: "#{ path }sass"
         css: "#{ path }css"


各バージョンのパッケージを残せるようにgulpタスクを変更

各バージョンのパッケージを残せるようにgulpタスクを変更

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -36,6 +36,7 @@
 		.pipe webpack output: filename: 'baser.js'
 		.pipe header banner, pkg: pkg, moment: moment
 		.pipe gulp.dest './dist/'
+		.pipe gulp.dest "./dist/v#{pkg.version}/"

 gulp.task 'compress', ->
 	gulp.src './dist/baser.js'
@@ -43,6 +44,7 @@
 		.pipe rename 'baser.min.js'
 		.pipe header banner, pkg: pkg, moment: moment
 		.pipe gulp.dest './dist/'
+		.pipe gulp.dest "./dist/v#{pkg.version}/"

 gulp.task 'test', ->
 	gulp.src './test/*.html'


gulpfile

gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -75,9 +75,9 @@
 #****************************
 gulp.task "jade", ->
   YOUR_LOCALS = {}
-  gulp.src(pkg.settings.app+"/jade/*.jade")
-  .pipe($.plumber({errorHandler: $.notify.onError('<%= error.message %>')}))
-  .pipe($.data((file)->
+  gulp.src pkg.settings.app+"/jade/*.jade"
+  .pipe $.plumber({errorHandler: $.notify.onError('<%= error.message %>')})
+  .pipe $.data (file)->
     if file
       reg = /\/([A-Za-z_0-9]+?)\.jade/
       path = file.path.match(reg)[1]
@@ -88,10 +88,10 @@
         return {"title":data[path]["title"],"keyword":data[path]["keyword"],"disc":data[path]["disc"],"path":path}
       else
         return {"title":null,"keyword":null,"desc":null}
-  ))
-  .pipe(jade(
-    pretty: true
-  )).pipe gulp.dest(pkg.settings.dist)
+
+  .pipe(jade(pretty: true))
+  .pipe gulp.dest pkg.settings.dist
+  .on 'end' , reload

@@ -111,6 +111,7 @@
   # .pipe $.uglify()
   .pipe gulp.dest pkg.settings.dist+"js/" # Output directory
   .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/js/'
+  .on 'end' , reload

 # VanillaJS
@@ -123,6 +124,7 @@
   .pipe buffer()
   # .pipe $.uglify()
   .pipe gulp.dest pkg.settings.dist+"js/" # Output directory
+  .on 'end' , reload

@@ -144,9 +146,10 @@
     "rem": false,
   )
   .pipe gulp.dest pkg.settings.dist+"style"
-  .pipe $.header cssbanner, pkg : pkg
-  .pipe replace("../", "./")
-  .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name
+  # .pipe $.header cssbanner, pkg : pkg
+  # .pipe replace("../", "./")
+  # .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name
+  .on 'end' , reload

 #stylus
@@ -158,6 +161,7 @@
     compress: true
   .pipe $.pleeease()
   .pipe gulp.dest pkg.settings.dist+"style"
+  .on 'end' , reload

@@ -194,7 +198,7 @@
     }))
     .pipe gulp.dest pkg.settings.dist+'images'
     .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/images'
-    .pipe($.size({title: 'images'}));
+    .pipe $.size({title: 'images'})

 gulp.task "clear", (i_done) ->
   return $.cache.clearAll(i_done);
@@ -322,12 +326,7 @@
   gulp.watch "app/**/*.js", ["js"]
   gulp.watch "app/**/*.coffee", ["script"]

-
-  gulp.watch "dist/**/*.html", reload
-  gulp.watch "dist/**/*.css", reload
-  gulp.watch "dist/**/*.js", reload
   return
-

 # watch
@@ -340,10 +339,6 @@

   gulp.watch "app/**/*.jade", ["jade"]
-
-  # gulp.watch('app/**/*.jade', reload);
-  # gulp.watch('app/**/*.scss', reload);
-
   # gulp.watch "app/**/*.scss", ['styleguide']
   gulp.watch "app/**/*.scss", ["scss"]
   gulp.watch "app/**/*.js", ["js"]
@@ -353,4 +348,5 @@
   gulp.watch pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/**/*.php', reload
   gulp.watch pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/**/*.css', reload
   gulp.watch pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/**/*.js', reload
+
   return


List task arrays on separate lines

List task arrays on separate lines

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -25,14 +25,32 @@
 gulpTaskLoader = GulpTaskLoader opts

 # Develop.
-gulp.task 'develop', (done)->
-    runSequence 'jade', 'imagemin', 'svgmin', 'sass:develop', 'angular-templatecache', 'webpack:develop', 'webserver', ()->
-        done()
+gulp.task 'develop', (done) ->
+  runSequence [
+    'jade'
+    'imagemin'
+    'svgmin'
+    'sass:develop'
+    'angular-templatecache'
+    'webpack:develop'
+    'webserver'
+    ],
+    () ->
+      done()

 # Build.
-gulp.task 'build', (done)->
-    runSequence 'jade', 'humans', 'robots', 'imagemin', 'svgmin', 'sass:build', 'webpack:build', ()->
-        done()
+gulp.task 'build', (done) ->
+  runSequence [
+    'jade'
+    'humans'
+    'robots'
+    'imagemin'
+    'svgmin'
+    'sass:build'
+    'webpack:build'
+    ],
+    () ->
+      done()

 # Default.
 gulp.task 'default', ['develop']


don't require global coffee; fixes #3

don't require global coffee; fixes #3

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -82,6 +82,8 @@
   nodemon
     script: server_main
     watch: [server_main]
+    execMap:
+      coffee: "#{modules_path}/.bin/coffee"
     env:
       PORT: process.env.PORT or 3000


AdminApp: helper method to return navigation items

--- client/admin/lib/index.coffee
+++ client/admin/lib/index.coffee
@@ -118,3 +118,5 @@
       if previousRoutes.length > 0
       then router.handleRoute previousRoutes.last
       else router.handleRoute router.getDefaultRoute()
+
+  fetchNavItems: (cb) -> cb NAV_ITEMS


fix broken help page

fix broken help page

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -46,7 +46,7 @@
     lineHeight : 'inherit'
     marginTop  : '0.7ex'

-exports.HelpPageUsageSection = rclass
+exports.HelpPageUsageSection = HelpPageUsageSection = rclass
     reduxProps :
         server_stats :
             loading             : rtypes.bool.isRequired


修正Table的远端排序不可用的BUG

修正Table的远端排序不可用的BUG

--- src/widget/list/table.coffee
+++ src/widget/list/table.coffee
@@ -364,12 +364,6 @@
 			else sortDirection = "asc"
 			column.set("sortDirection", sortDirection)

-			if @fire("sortDirectionChange", @, {
-				column: column
-				sortDirection: sortDirection
-			}) is false
-				return
-
 			collection = @_realOriginItems or @_realItems
 			if not collection then return

@@ -396,18 +390,32 @@
 			if @_sortMode is "remote"
 				if collection instanceof cola.EntityList
 					invoker = collection._providerInvoker
-					if invoker
-						parameter = invoker.ajaxService.get("parameter")
-						if not parameter
-							invoker.invokerOptions.data = parameter = {}
-						else if typeof parameter isnt "object" or parameter instanceof cola.EntityList or parameter instanceof Date
-							throw new cola.Exception("Can not set sort parameter automatically.")
-						else if parameter instanceof cola.Entity
-							parameter = parameter.toJSON()
-						parameter[cola.setting("defaultSortParameter") or sort] = criteria
-
-						cola.util.flush(collection)
-			else
+
+				if @fire("sortDirectionChange", @, {
+					column: column
+					invoker: invoker
+					sortDirection: sortDirection
+				}) is false
+					return
+
+				if collection instanceof cola.EntityList and invoker
+					parameter = invoker.ajaxService.get("parameter")
+					if not parameter
+						invoker.invokerOptions.data = parameter = {}
+					else if typeof parameter isnt "object" or parameter instanceof cola.EntityList or parameter instanceof Date
+						throw new cola.Exception("Can not set sort parameter automatically.")
+					else if parameter instanceof cola.Entity
+						parameter = parameter.toJSON()
+					parameter[cola.setting("defaultSortParameter") or sort] = criteria
+
+					cola.util.flush(collection)
+			else
+				if @fire("sortDirectionChange", @, {
+					column: column
+					sortDirection: sortDirection
+				}) is false
+					return
+
 				@_sortCriteria = criteria
 				@_refreshItems()
 		return


FShelpter: updated the s3.upload method to separete user and group content

--- client/ide/fs/fshelper.coffee
+++ client/ide/fs/fshelper.coffee
@@ -215,16 +215,16 @@
     getGroupRelated : (group, name)->
       "#{KD.config.uploadsUriForGroup}/#{group}/#{name}"

-      #user
-      # args = {name,  content}
+    upload : (name, content, bucket, path, callback)->
+      args = {name, bucket, path, content}

       KD.getSingleton('vmController').run
         method    : 's3.store'
         withArgs  : args
       , (err, res)->
-        console.log "upload", err, res
-        # if err then callback err
-        # else callback null, FSHelper.s3.get name
+        return callback err if err
+        filePath = if bucket is "groups" then FSHelper.s3.getGroupRelated path, name else FSHelper.s3.get name
+        callback null, filePath

     remove : (name, callback)->
       vmController = KD.getSingleton 'vmController'


Corrects toString methods on expression components

--- javascripts/lib/math/expression_components.coffee
+++ javascripts/lib/math/expression_components.coffee
@@ -162,17 +162,15 @@
     class_mixer(Exponentiation)

     class Pi extends ExpressionComponent
-      toDisplay: -> "π"
       toString: -> "PI()"
     class_mixer(Pi)

     class Addition extends ExpressionComponent
-      toDisplay: -> "Add()"
+      toString: -> "Add()"
       isOperator: -> true
     class_mixer(Addition)

     class Subtraction extends ExpressionComponent
-      toDisplay: -> "-"
       isOperator: -> true
       toString: -> "Sub()"
     class_mixer(Subtraction)


Disable table auto-reload

Disable table auto-reload

--- client/pages/TablePage.coffee
+++ client/pages/TablePage.coffee
@@ -27,6 +27,6 @@
         data: "table/#{props.match.params.userList}/#{props.match.params.id}"
         me: "me"

-    timeout: 20000
+    #timeout: 20000

 export default ConnectedComponent(TablePage, options)


Mark unread board posts

Mark unread board posts

Fixes #7

--- static/script/settlers.coffee
+++ static/script/settlers.coffee
@@ -133,7 +133,7 @@
     after_refresh:	(response, pager) ->
       __mark_unread = (post) ->
         if post.id > response.last_board
-          $('#chat_post_' + post.id + ' .chat-post-unread-badge').show()
+          $('#chat_post_' + post.id + ' .chat-post-unread').show()

       __mark_unread post for post in response.page.records


Rename VCS ignore config setting to fuzzyFinder.hideVcsIgnoredPaths

--- src/packages/fuzzy-finder/lib/load-paths-task.coffee
+++ src/packages/fuzzy-finder/lib/load-paths-task.coffee
@@ -9,11 +9,10 @@
     rootPath = project.getPath()
     ignoredNames = config.get('fuzzyFinder.ignoredNames') ? []
     ignoredNames = ignoredNames.concat(config.get('core.ignoredNames') ? [])
-    ignoreGitIgnoredFiles =  config.get('core.hideGitIgnoredFiles')

     command = require.resolve 'nak'
     args = ['--list', rootPath]
-    args.unshift('--addVCSIgnores') if config.get('nak.addVCSIgnores')
+    args.unshift('--addVCSIgnores') if config.get('fuzzyFinder.hideVcsIgnoredPaths')
     args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
     args.unshift('--follow')


Set internal state for whether a simulation is time-based depending on settings on load. [#159180706]

--- src/code/stores/simulation-store.coffee
+++ src/code/stores/simulation-store.coffee
@@ -116,6 +116,7 @@

   onImport: (data) ->
     _.merge @settings, data.settings.simulation
+    @settings.isTimeBased = data.settings.simulationType is AppSettingsStore.store.SimulationType.time
     hasCollectors = _.filter(data.nodes, (node) -> node.data.isAccumulator).length > 0
     @onSetStepUnits(unit: data.settings.simulation.stepUnits, hasCollectors)
     @notifyChange()


autocommit

autocommit


Remove unnecessary log output

Remove unnecessary log output

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -4,7 +4,6 @@

 g.task 'watch', ->
   g.watch ['app/src/**/*.elm'],['elm']
-  console.log electron.start
   electron.start()
   g.watch ['app/app.js', 'app/*.html'], ->
     electron.restart()


inject proposal

inject proposal

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -55,15 +55,15 @@
     .pipe gulp.dest outapp

-buildHtml = (transform = gutil.noop()) ->
+# reloader will inject <script> tag
+htmlInject = gutil.noop()
+
+# copy .html-files
+gulp.task 'html', ->
   gulp.src paths.html
-    .pipe transform
+    .pipe htmlInject
     .pipe changed outapp
     .pipe gulp.dest outapp
-
-
-# copy .html-files
-gulp.task 'html', -> buildHtml()

 # compile less
@@ -85,7 +85,7 @@
     .pipe gulp.dest outui

   # inject scripts in html
-  #buildHtml reloader.inject()
+  #htmlInject = reloader.inject()

   # watch rebuilt stuff
   gulp.watch "#{outui}/**/*", reloader.onChange
@@ -96,7 +96,7 @@

 gulp.task 'default', ['package', 'coffee', 'html', 'less']

-gulp.task 'watch', ['reloader'], ->
+gulp.task 'watch', ['reloader', 'html'], ->
   # watch to rebuild
   sources = (v for k, v of paths)
   gulp.watch sources, ['default']


Update gulpfile.coffee

Update gulpfile.coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -12,5 +12,5 @@
   sass("./assets/css/**.scss").pipe(gulp.dest("./assets/css"))

 gulp.task "watch", ->
-  gulp.watch("./assets/css/**.scss", ["compile:scss"])
-  gulp.watch("./assets/js/**.coffee", ["compile:coffee"])+  gulp.watch("./assets/js/**.coffee", ["compile:coffee"])
+  gulp.watch("./assets/css/**.scss", ["compile:scss"])


Add Watch Task

Add Watch Task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -8,16 +8,18 @@

 SRC = 'src'
 DEST = 'out'
+HAML_PATH = "#{SRC}/haml/**/*.haml"
+COFFEE_PATH = "#{SRC}/coffee/**/*.coffee"

 gulp.task 'default', ['haml', 'coffee', 'bower']

 gulp.task 'haml', ->
-  gulp.src "#{SRC}/haml/**/*.haml"
+  gulp.src HAML_PATH
     .pipe haml()
     .pipe gulp.dest DEST

 gulp.task 'coffee', ->
-  gulp.src "#{SRC}/coffee/**/*.coffee"
+  gulp.src COFFEE_PATH
     .pipe sourcemaps.init()
     .pipe coffee()
     .pipe sourcemaps.write()
@@ -30,5 +32,9 @@
 gulp.task 'clean', (callback) ->
   del DEST, callback

+gulp.task 'watch', ['haml', 'coffee'], ->
+  gulp.watch HAML_PATH, ['haml']
+  gulp.watch COFFEE_PATH, ['coffee']
+
 gulp.task 'serve', ->
   serve DEST


Replace gulp-nodemon by nodemon due to a huge drop of performance when running asynchronously with other task.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -6,7 +6,7 @@
 plumber    = require('gulp-plumber')
 livereload = require('gulp-livereload')
 compass    = require('gulp-compass')
-nodemon    = require('gulp-nodemon')
+nodemon    = require('nodemon')
 coffee     = require('gulp-coffee')
 replace    = require('gulp-replace')
 browserify = require('gulp-browserify')
@@ -100,9 +100,8 @@
     .pipe(notify('Compass compiled without errors'))
 )

-gulp.task('server', (done)->
+gulp.task('server', (cb)->
   nodemon({
-    script: 'server.coffee'
-    quiet: true
+    './server.coffee'
   })
 )


Fix build dir

Fix build dir

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -4,7 +4,7 @@

 config =
   paths:
-    dist: '/build'
+    dist: 'build/**/*'

 ghpages =
   push: true


improve error reporting

improve error reporting

--- services/clsi/app/coffee/LockManager.coffee
+++ services/clsi/app/coffee/LockManager.coffee
@@ -16,11 +16,13 @@
 			stale: @LOCK_STALE
 		Lockfile.lock path, lockOpts, (error) ->
 			return callback new Errors.AlreadyCompilingError("compile in progress")	if error?.code is 'EEXIST'
-			fs.lstat path, (err, statLock)->
-				fs.lstat Path.dirname(path), (err, statDir)->
-					if error?
-						logger.err error:error, path:path, statLock:statLock, statDir: statDir, "unable to get lock"
-						return callback(error)
+				if error?
+					fs.lstat path, (statLockErr, statLock)->
+						fs.lstat Path.dirname(path), (statDirErr, statDir)->
+							fs.readdir Path.dirname(path), (readdirErr, readdirDir)->
+							logger.err error:error, path:path, statLock:statLock, statLockErr:statLockErr, statDir:statDir, statDirErr: statDirErr, readdirErr:readdirErr, readdirDir:readdirDir, "unable to get lock"
+							return callback(error)
+				else
 					runner (error1, args...) ->
 						Lockfile.unlock path, (error2) ->
 							error = error1 or error2


neo4j: change hardcoded koding id

neo4j: change hardcoded koding id

--- server/lib/server/graph/graph.coffee
+++ server/lib/server/graph/graph.coffee
@@ -5,7 +5,7 @@
   constructor:(config)->
     # todo remove hardcoded id

-    @groupId   = "5150c743f2589b107d000007"
+    @groupId   = "5196fcb2bc9bdb0000000027"
     @groupName = "koding"
     @db = new neo4j.GraphDatabase(config.host + ":" + config.port);


logs out when a redis event occurs

logs out when a redis event occurs

--- services/document-updater/app.coffee
+++ services/document-updater/app.coffee
@@ -138,6 +138,15 @@
 			process.exit()
 		, 10000

+watchForEvent = (eventName)->
+	docUpdaterRedisClient.on eventName, (e)->
+		console.log "redis event: #{eventName} #{e}"
+
+events = ["connect", "ready", "error", "close", "reconnecting", "end"]
+for eventName in events
+	watchForEvent(eventName)
+
+
 port = Settings.internal?.documentupdater?.port or Settings.apis?.documentupdater?.port or 3003
 host = Settings.internal.documentupdater.host or "localhost"
 if !module.parent # Called directly


Fix Room model's removeByTypeContainingUsername.  usernames field missing trailing 's'

--- packages/rocketchat-lib/server/models/Rooms.coffee
+++ packages/rocketchat-lib/server/models/Rooms.coffee
@@ -332,6 +332,6 @@
 	removeByTypeContainingUsername: (type, username) ->
 		query =
 			t: type
-			username: username
+			usernames: username

 		return @remove query


whitespace

whitespace

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -181,7 +181,6 @@
     t.ok "klass" in css, "Has explicit class"
     t.ok "root" in css, "Has root"
     t.end()
-

   test 'config handlers', (t) ->
     t.ok handlers = world.handlers_for('run'), "handlers exist"


Optional BeanShellWrapper at runtime

Optional BeanShellWrapper at runtime

--- web/doppio-jvm/scripts/demo/doppio-api.coffee
+++ web/doppio-jvm/scripts/demo/doppio-api.coffee
@@ -66,7 +66,7 @@
         end_untar = (new Date()).getTime()
         @log? "Untarring took a total of #{end_untar-start_untar}ms."

-    run: (studentCode, finished_cb) =>
+    run: (studentCode, beanshellWrapperName, finished_cb) =>
         ###
             Runs the given Java Code.
             Note, this does not recognize classes.
@@ -76,8 +76,8 @@
         fname = 'program.bsh'
         node.fs.writeFileSync(fname, studentCode)
         stdin = -> "\n"
-        if @beanshellWrapperName?
-            class_args = [@beanshellWrapperName]
+        if beanshellWrapperName?
+            class_args = [beanshellWrapperName]
         else
             class_args = [fname]
         finish_cb = =>
@@ -103,7 +103,7 @@
             cb = =>
                 @log? 'Aborted Successfully'
                 @rs = null
-                finished_cb()
+                finished_cb() if finished_cb?
             @rs.async_abort(cb)
         else
             @log? 'No Run Detected'


add extra object check for id detection/extraction

--- lib/assets/javascripts/stark/model_util.js.coffee
+++ lib/assets/javascripts/stark/model_util.js.coffee
@@ -35,10 +35,10 @@

       return false

-    # search data.params hash
+    # search for id
     if data.params? && data.params[id]?
       @[id] = data.params[id]
-    else if data[name]?
+    else if data[name]? && _.isObject(data[name]) && data[name].id?
       @[id] = data[name].id
     else if data[id]?
       @[id] = data[id]
@@ -46,4 +46,5 @@
       return false

     @id = @[id] if set_id
+
     return true


Add a description for each package

Add a description for each package

--- package.cson
+++ package.cson
@@ -1 +1,2 @@
-'main': 'lib/link'
+'main': './lib/link'
+'description': 'Open the link under the cursor in a browser using `ctrl-O`.'


Use coffescript's string interpolation

Use coffescript's string interpolation

--- packages/rocketchat-slashcommands-join/join.coffee
+++ packages/rocketchat-slashcommands-join/join.coffee
@@ -27,7 +27,7 @@
 					_id: Random.id()
 					rid: item.rid
 					ts: new Date
-					msg: 'The channel `#' + channel + '` does not exist.'#TODO: Make this a language setting
+					msg: "The channel `\##{channel}` does not exist."#TODO: Make this a language setting
 				}
 				return


Change event hosting service to connpass from Doorkeeper

--- scripts/lt.coffee
+++ scripts/lt.coffee
@@ -19,7 +19,7 @@
   "会場手配"
   "タイムスケジュール作成"
   "場内での飲食物に関する調査"
-  "Doorkeeperでのイベント作成"
+  "connpassでのイベント作成"
   "懇親会の企画"
   "当日の運営手伝いマンにやってもらうことをリスト化"
   "当日の必要物品リストの作成"


Thanks shyguy

Thanks shyguy

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -123,6 +123,10 @@

   # Hold my beer, I'm about to computer a computer
   if /drewzar/i.test(name)
+    name = 'drewzar'
+
+  # Lets just get the whole channel in this function
+  if /shyguy/i.test(name)
     name = 'drewzar'

   return name


improve guest.obtain

improve guest.obtain

--- workers/social/lib/social/models/guest.coffee
+++ workers/social/lib/social/models/guest.coffee
@@ -60,31 +60,49 @@

   recycle:-> @constructor.recycle this # YAGNI?

-  @obtain = secure (client, clientId, callback)->
-    [callback, clientId] = [clientId, callback] unless callback
-    JAccount = require './account'
-    createId = require 'hat'
-    {delegate} = client?.connection
-    if delegate instanceof JAccount
-      callback error 'Logged in user cannot obtain a guest account!'
-    else
-      leaseId = createId()
-      @update {status: 'pristine'}, $set:{status: 'leasing', leaseId}, (err)=>
-        if err then callback error err
-        else
-          @one {leaseId}, (err, guest)=>
-            if err then callback error err
-            else unless guest?
-              callback error "We've reached maximum occupancy for guests.  Please try again later."
-            else
-              @update {_id:guest.getId()}, {
-                $unset    :
-                  leaseId : 1
-                $set      :
-                  status  : 'in use'
-              }, (err)->
-                if err then callback error err
-                else callback null, guest
+  @obtain = do->
+    obtaining = {}
+    secure (client, clientId, callback)->
+      console.log 'guest.obtain is called', clientId
+      [callback, clientId] = [clientId, callback] unless callback
+      JAccount = require './account'
+      createId = require 'hat'
+      if clientId?
+        if obtaining[clientId]
+          console.log 'already obtaining', clientId
+          @once "ready.#{clientId}", (guest)-> console.log 'finally got', clientId; callback null, guest
+          return
+        obtaining[clientId] = yes
+      {delegate} = client?.connection
+      if delegate instanceof JAccount
+        callback error 'Logged in user cannot obtain a guest account!'
+      else
+        leaseId = createId()
+        @update {status: 'pristine'}, $set:{
+          status    : 'leasing'
+          leasedAt  : new Date
+          leaseId
+        }, (err)=>
+          if err then callback error err
+          else
+            @one {leaseId}, (err, guest)=>
+              console.log guest?.prune()
+              if err then callback error err
+              else unless guest?
+                callback error "We've reached maximum occupancy for guests.  Please try again later."
+              else
+                @update {_id:guest.getId()}, {
+                  $unset    :
+                    leaseId : 1
+                  $set      :
+                    status  : 'in use'
+                }, (err)=>
+                  if err then callback error err
+                  else
+                    console.log 'successfully obtained', clientId
+                    @emit "ready.#{clientId}", guest
+                    delete obtaining[clientId]
+                    callback null, guest

   fetchStorage: secure (client, options, callback)->
     JAppStorage = require './appstorage'


JComputeStack: fix coffeelint errors

JComputeStack: fix coffeelint errors

--- workers/social/lib/social/models/stack.coffee
+++ workers/social/lib/social/models/stack.coffee
@@ -170,13 +170,13 @@

       JGroup = require './group'

-      JGroup.one { slug: data.groupSlug }, (err, group) =>
+      JGroup.one { slug: data.groupSlug }, (err, group) ->
         return callback err  if err or not group

-        updateGroupResourceUsage data, group, 'increment', (err) =>
+        updateGroupResourceUsage data, group, 'increment', (err) ->
           return callback err  if err

-          JComputeStack.create data, (err, stack) =>
+          JComputeStack.create data, (err, stack) ->
             if err
             then updateGroupResourceUsage data, group, 'decrement', -> callback err
             else callback null, stack


update all event article counts, check current user before updating event article count

--- collections/userEvents.coffee
+++ collections/userEvents.coffee
@@ -63,12 +63,13 @@
         lastModifiedByUserName: user.profile.name
       }})
   updateUserEventArticleCount: (id, countModifier) ->
-    event = UserEvents.findOne(id)
-    UserEvents.update(id, {$set: {articleCount: event.articleCount + countModifier}})
+    if Meteor.user()
+      event = UserEvents.findOne(id)
+      UserEvents.update(id, {$set: {articleCount: event.articleCount + countModifier}})

 # Add article count field to events
 if Meteor.isServer
   Meteor.startup ->
-    UserEvents.find({articleCount: {$exists: false}}).forEach (event) ->
+    UserEvents.find().forEach (event) ->
       count = grid.Articles.find({userEventId: event._id}).count()
       UserEvents.update(event._id, {$set: {articleCount: count}})


zindex

zindex

--- vendor/assets/javascripts/better_colorpicker/better_colorpicker.coffee
+++ vendor/assets/javascripts/better_colorpicker/better_colorpicker.coffee
@@ -52,6 +52,7 @@
         100
       vPosition: (w, h) ->
         100
+      zPosition: 1
       anchor: $('body')
       autoClose: true
       outputType: 'rgb'
@@ -261,14 +262,13 @@
     unless @isOpen
       @elem.openMenu 0, 0, @args.onOpen(), @args.anchor, @args.autoClose, @args

-      x = @args.hPosition @elem.jqObj.outerWidth(), @elem.jqObj.outerHeight();
-      y = @args.vPosition @elem.jqObj.outerWidth(), @elem.jqObj.outerHeight();
-      z = @args.zPosition @elem.jqObj.outerWidth(), @elem.jqObj.outerHeight();
+      x = @args.hPosition @elem.jqObj.outerWidth(), @elem.jqObj.outerHeight()
+      y = @args.vPosition @elem.jqObj.outerWidth(), @elem.jqObj.outerHeight()

       @elem.jqObj.css
         left: x
         top: y
-        z-index: z
+        zIndex: @args.zPosition
     else
       @close()


Refactor

Refactor

--- test/fileDiscoverySpec.coffee
+++ test/fileDiscoverySpec.coffee
@@ -76,7 +76,7 @@
         auth = new Authrc

       it 'should not find the file and the content not exists', ->
-          expect(auth.exists()).to.be.false
+        expect(auth.exists()).to.be.false

     describe 'with existent $HOME file', ->
       homePath = process.env[getHomeVar()]
@@ -92,4 +92,5 @@

       it 'should find the file and the content exists', ->
         expect(auth.exists()).to.be.true
+        expect(auth.isGlobalFile()).to.be.true


adding logging for timestamp messages.

adding logging for timestamp messages.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -50,6 +50,11 @@
       unless bot.info.id == author.id
         self.receive new Robot.LeaveMessage(author)

+    bot.on "TimestampMessage", withAuthor (id, created, room, user, body, author) ->
+      console.log "Timestamp was sent to the room #{room}"
+      if author is null
+        console.log "Timestamp Message had a null author. Author: #{author}"
+
     bot.Me (err, data) ->
       bot.info = data.user
       bot.name = bot.info.name


Fix lmo url service specs

Fix lmo url service specs

--- lineman/spec/services/lmo_url_service_spec.coffee
+++ lineman/spec/services/lmo_url_service_spec.coffee
@@ -15,16 +15,16 @@
           host: 'localhost'

       it "handles a group without subdomain on localhost", ->
-        expect(@subject.group(@group)).toBe 'http://localhost:3000/g/key/name'
+        expect(@subject.group(@group)).toBe "http://localhost:3000/g/#{@group.key}/name"

       it "handles a group with subdomain on localhost", ->
         @group.updateFromJSON subdomain: 'subdomain'
-        expect(@subject.group(@group)).toBe 'http://subdomain.localhost:3000/'
+        expect(@subject.group(@group)).toBe "http://subdomain.localhost:3000/"

       it "handles a subgroup with subdomain on localhost", ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'http://subdomain.localhost:3000/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "http://subdomain.localhost:3000/g/#{subgroup.key}/name-subname"

     describe 'on loom.io', ->
       beforeEach ->
@@ -34,7 +34,7 @@
           ssl: true

       it 'handles a group without subdomain on loom.io', ->
-        expect(@subject.group(@group)).toBe 'https://loom.io/g/key/name'
+        expect(@subject.group(@group)).toBe "https://loom.io/g/#{@group.key}/name"

       it 'handles a group with subdomain on loom.io', ->
         @group.updateFromJSON subdomain: 'subdomain'
@@ -43,7 +43,7 @@
       it 'handles a subgroup with subdomain on loom.io', ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'https://subdomain.loom.io/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "https://subdomain.loom.io/g/#{subgroup.key}/name-subname"

     describe 'on loomio.org', ->
       beforeEach ->
@@ -52,16 +52,16 @@
           default_subdomain: 'www'

       it 'handles a group without subdomain on loomio.org', ->
-        expect(@subject.group(@group)).toBe 'http://www.loomio.org/g/key/name'
+        expect(@subject.group(@group)).toBe "http://www.loomio.org/g/#{@group.key}/name"

       it 'handles a group with subdomain on loomio.org', ->
         @group.updateFromJSON subdomain: 'subdomain'
-        expect(@subject.group(@group)).toBe 'http://subdomain.loomio.org/'
+        expect(@subject.group(@group)).toBe "http://subdomain.loomio.org/"

       it 'handles a subgroup with subdomain on loomio.org', ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'http://subdomain.loomio.org/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "http://subdomain.loomio.org/g/#{subgroup.key}/name-subname"

     describe 'on loomio.anotherdomain.com', ->
       beforeEach ->
@@ -70,13 +70,13 @@
           default_subdomain: null

       it 'handles a group without subdomain on loomio.anotherdomain.com', ->
-        expect(@subject.group(@group)).toBe 'http://loomio.anotherdomain.com/g/key/name'
+        expect(@subject.group(@group)).toBe "http://loomio.anotherdomain.com/g/#{@group.key}/name"

       it 'handles a group with subdomain on loomio.anotherdomain.com', ->
         @group.updateFromJSON subdomain: 'subdomain'
-        expect(@subject.group(@group)).toBe 'http://subdomain.loomio.anotherdomain.com/'
+        expect(@subject.group(@group)).toBe "http://subdomain.loomio.anotherdomain.com/"

       it 'handles a subgroup with subdomain on loomio.anotherdomain.com', ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'http://subdomain.loomio.anotherdomain.com/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "http://subdomain.loomio.anotherdomain.com/g/#{subgroup.key}/name-subname"


if status of stack is Destroying user can delete stack template

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -496,7 +496,10 @@
     success: (client, callback) ->

       JComputeStack = require '../stack'
-      JComputeStack.some { baseStackId: @_id }, { limit: 1 }, (err, stacks) ->
+      JComputeStack.some {
+        baseStackId: @_id
+        'status.state': { $ne: 'Destroying' }
+      }, { limit: 1 }, (err, stacks) ->
         result = stacks?.length > 0
         callback err, result


Always switch to default sort

Always switch to default sort

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -219,6 +219,13 @@
   updateFilters: (_e, newFilters) =>
     newFilters = _.extend {}, @state.filters, newFilters

+    if @state.filters.status != newFilters.status
+      newFilters.sort =
+        if newFilters.status in [3, 4, 5]
+          'updated_desc'
+        else
+          'ranked_desc'
+
     if !_.isEqual @state.filters, newFilters
       @setState filters: newFilters, ->
         $(document).trigger 'beatmap:search:start'


Use geocoder API to get missing municipality.

--- server-src/legacy-redirector.coffee
+++ server-src/legacy-redirector.coffee
@@ -1,5 +1,7 @@
 express = require('express')
 _ = require('underscore')
+config = require 'config'
+http = require 'http'

 app = express()

@@ -38,7 +40,7 @@
         return null
     services.join ','

-extractStreetAddress = (req) ->
+extractStreetAddress = (req, municipalityParameter) ->
     addressParts = undefined
     addressString = req.query.address or req.query.addresslocation
     if addressString == undefined
@@ -49,21 +51,20 @@
     municipality = undefined
     if addressParts.length == 2
         municipality = addressParts[1].trim()
-    else
-        municipality = 'helsinki'
+    if not municipality?
+        municipality = municipalityParameter
     streetPart = addressParts[0].trim()
     numberIndex = streetPart.search(/\d/)
     street = streetPart.substring(0, numberIndex).trim()
     numberPart = streetPart.substring(numberIndex)
-    {
-        municipality: municipality.toLowerCase()
-        street: street.toLowerCase().replace(/\s+/g, '+')
-        number: numberPart.toLowerCase()
-    }
+
+    municipality: municipality?.toLowerCase()
+    street: street.toLowerCase().replace(/\s+/g, '+')
+    number: numberPart.toLowerCase()

 extractMunicipality = (req) ->
     municipality = req.query.city
-    if municipality != undefined then return municipality
+    if municipality? then return municipality

     region = req.query.region
     if not region then return null
@@ -101,7 +102,7 @@

     specs.municipality = extractMunicipality(req)
     specs.services = extractServices(req)
-    specs.address = extractStreetAddress(req)
+    specs.address = extractStreetAddress(req, specs.municipality)
     if specs.address != null
         specs.serviceCategory = dig('service')
     specs
@@ -174,10 +175,50 @@
         path.push specs.unit
     protocol + path.join('/') + generateQuery(specs, resource, originalUrl) + fragment

+
+getMunicipalityFromGeocoder = (address, language, callback) ->
+    municipality = address.municipality
+    if municipality? and municipality.length
+        callback municipality
+        return
+
+    timeout = setTimeout (-> callback null), 3000
+
+    url = "#{config.service_map_backend}/address/?language=#{language}&number=#{address.number}&street=#{address.street}&page_size=1"
+    request = http.get url, (apiResponse) ->
+        if apiResponse.statusCode != 200
+            clearTimeout timeout
+            callback null
+            return
+        respData = ''
+        apiResponse.on 'data', (data) ->
+            respData += data
+        apiResponse.on 'end', ->
+            addressInfo = JSON.parse respData
+            clearTimeout timeout
+            municipality = addressInfo?.results?[0]?.street?.municipality
+            callback municipality
+            return
+    request.on 'error', (error) =>
+        console.error 'Error geocoding using servicemap API', error
+        callback null
+        return
+
 redirector = (req, res) ->
     specs = extractSpecification(req)
-    url = generateUrl(specs, req.originalUrl)
-    res.redirect 301, url
-    return
+    resource = getResource specs
+    if resource == 'address' and not specs.address.municipality?
+        getMunicipalityFromGeocoder specs.address, specs.language.id, (municipality) ->
+            if municipality?
+                specs.address.municipality = municipality
+            else
+                specs.address.municipality = 'helsinki'
+            url = generateUrl(specs, req.originalUrl)
+            res.redirect 301, url
+        return
+    else
+        url = generateUrl(specs, req.originalUrl)
+        res.redirect 301, url
+        return

 module.exports = redirector


Handle doc urls which remain in old server.

--- server-src/legacy-redirector.coffee
+++ server-src/legacy-redirector.coffee
@@ -94,6 +94,9 @@
     specs.originalPath = _.filter req.url.split('/'), (s) -> s.length > 0
     if 'embed' in specs.originalPath
         specs.isEmbed = true
+    if 'esteettomyys' in specs.originalPath or 'yllapito' in specs.originalPath
+        specs.override = true
+        return specs
     specs.language = extractLanguage(req, specs.isEmbed)
     if specs.language.isAlias == true
         return specs
@@ -209,6 +212,10 @@

 redirector = (req, res) ->
     specs = extractSpecification(req)
+    if specs.override == true
+        url = req.originalUrl.replace /\/rdr\/?/, 'http://www.hel.fi/karttaupotus/'
+        res.redirect 301, url
+        return
     resource = getResource specs
     if resource == 'address' and not specs.address.municipality?
         getMunicipalityFromGeocoder specs.address, specs.language.id, (municipality) ->


handle requests for favorite changing

handle requests for favorite changing

--- server/controllers/mailboxes.coffee
+++ server/controllers/mailboxes.coffee
@@ -53,13 +53,24 @@

     Promise.join pBox, pAccount, (box, account) ->

-        path = box.path
-        parentPath = path.substring 0, path.lastIndexOf(box.label)
-        newPath = parentPath + req.body.label
+        if req.body.label

-        account.imap_renameBox path, newPath
-        .then -> box.renameWithChildren newPath
-        .return account
+            path = box.path
+            parentPath = path.substring 0, path.lastIndexOf(box.label)
+            newPath = parentPath + req.body.label
+
+            account.imap_renameBox path, newPath
+            .then -> box.renameWithChildren newPath
+            .return account
+
+        else if req.body.favorite?
+
+            favorites = _.without account.favorites, box.id
+            favorites.push box.id if req.body.favorite
+            account.favorites = favorites
+            account.savePromised()
+
+        else throw new BadRequest 'Unsuported request for mailbox update'

     .then (account) -> account.toObjectWithMailbox()
     .then (account) -> res.send account


fix start a new conversation shown for all users

Former-commit-id: c6f96f7cce37f10d29fa8256b1ad6529b12f19b8 [formerly 90d2dbf93c296509d33d6196751f50ba5e97928b] [formerly a230cb68f55a2b79cb159b3bb57a04a9c036e617 [formerly a230cb68f55a2b79cb159b3bb57a04a9c036e617 [formerly ee739ee3fe66cd4213db1618530cc2e13a0bc51e]]]
Former-commit-id: 6bb64e3ccd41942460415a8e678756175d61f849 [formerly b9dc04e58c986028175a90b9064ece6f7702c1ac]
Former-commit-id: 3de82714f085b8e27c86107d9f9c82bb3adfa29e
Former-commit-id: b89e04508e4dc208e2a7b9a3cb016e937c42e642

--- app/assets/javascripts/backbone/entities/user.coffee
+++ app/assets/javascripts/backbone/entities/user.coffee
@@ -369,7 +369,7 @@

   App.reqres.setHandler "auth:can_create_proposal", ->
     current_user = AUTH_API.get_current_user()
-    current_user && ( current_user.isManager() || App.request("tenant:get").get('enable_user_conversations') )
+    AUTH_API.current_user_logged_in() && ( current_user.isManager() || App.request("tenant:get").get('enable_user_conversations') )

   App.reqres.setHandler "auth:can_edit_proposal", (proposal) ->
     current_user = AUTH_API.get_current_user()


read from ⎕ on console

read from ⎕ on console

--- src/vocabulary/quad.coffee
+++ src/vocabulary/quad.coffee
@@ -1,10 +1,11 @@
 addVocabulary

-  'get_⎕': ->
+  'get_⎕': cps (_, _1, _2, callback) ->
     if typeof window?.prompt is 'function'
-      new A(prompt('⎕:') or '')
+      setTimeout (-> callback new A(prompt('⎕:') or '')), 0
     else
-      nonceError 'Reading from ⎕ is not implemented.'
+      process.stdout.write '⎕:\n'
+      readline '      ', (line) -> callback exec new A(line).toSimpleString()

   'set_⎕': (x) ->
     s = format(x).join('\n') + '\n'


Social: add bongo endpoint for searching topics

--- workers/social/lib/social/models/socialapi/channel.coffee
+++ workers/social/lib/social/models/socialapi/channel.coffee
@@ -31,6 +31,8 @@
         unfollow:
           (signature Object, Function)
         fetchFollowedChannels:
+          (signature Object, Function)
+        searchTopics:
           (signature Object, Function)

     schema             :
@@ -96,6 +98,9 @@
     options.channelId = options.id
     @doRequest 'fetchChannelActivities', client, options, callback

+  @searchTopics = secure (client, options = {}, callback)->
+    @doRequest 'searchTopics', client, options, callback
+
   @fetchPopularTopics = secure (client, options = {}, callback)->
     @doRequest 'fetchPopularTopics', client, options, callback


Improved load ob objects in local storage / race condition during init/overwrite of new already loaded data.

--- app/assets/javascripts/app/lib/collection.js.coffee
+++ app/assets/javascripts/app/lib/collection.js.coffee
@@ -76,23 +76,27 @@

     return if _.isEmpty( params.data )

+    localStorage = params.localStorage
+
     if _.isArray( params.data )
       for object in params.data
 #        console.log( 'load ARRAY', object)
-        App[params.type].refresh( object, options: { clear: true } )
+        if !localStorage || localStorage && !App[ params.type ].exists( object['id'] )
+          App[ params.type ].refresh( object, options: { clear: true } )

         # remember in store if not already requested from local storage
-        if !params.localStorage
+        if !localStorage
           App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, data: [ object ] } )
       return

 #    if _.isObject( params.data )
     for key, object of params.data
 #      console.log( 'load OB', object)
-      App[params.type].refresh( object, options: { clear: true } )
+      if !localStorage || localStorage && !App[ params.type ].exists( object['id'] )
+        App[ params.type ].refresh( object, options: { clear: true } )

       # remember in store if not already requested from local storage
-      if !params.localStorage
+      if !localStorage
         App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, data: [ object ] } )

   find: ( type, id, callback ) ->


Add methods for extending or modifiying source object.

--- app/assets/javascripts/mixingpanel/source.js.coffee
+++ app/assets/javascripts/mixingpanel/source.js.coffee
@@ -21,6 +21,12 @@
       source: qsObj.utm_source
       medium: qsObj.utm_medium
       campaign: qsObj.utm_campaign
+
+  appendSources: (sources) ->
+    $.extend(@sources, sources)
+
+  setValueCallback: (callback) ->
+    @getValue = callback if typeof callback is "function"

   getValue: () ->
     if @utm.medium? or @utm.campaign?


Allows next/prev month via cell selection.

--- src/wave-datepicker.coffee
+++ src/wave-datepicker.coffee
@@ -147,7 +147,9 @@
       @$datepicker.on 'mousedown', @_cancelEvent
       @$datepicker.on 'click', '.js-wdp-calendar-cell', @_selectDate
       @$datepicker.on 'click', '.js-wdp-prev', @prev
+      @$datepicker.on 'click', '.js-wdp-prev-select', @prevSelect
       @$datepicker.on 'click', '.js-wdp-next', @next
+      @$datepicker.on 'click', '.js-wdp-next-select', @nextSelect
       @$datepicker.on 'click', '.js-wdp-shortcut', @_onShortcutClick

     # Updates the picker with the current date.
@@ -210,8 +212,9 @@
           if (index++) is 0
             html.push '
'
           d = prevMonth.add('days', -1).date()
+          formattedPrevMonth = @_formatDate new Date(@_state.year, @_state.month - 1, d)
           # + 1 because element at index zero is the

-          html[6 - i + 1] = "#{d}	"
+          html[6 - i + 1] = "#{d}	"
           paddingStart++

       # For formatting purposes in the following loop.
@@ -231,9 +234,10 @@
         n = paddingStart + daysInMonth
         while (n++) % 7 isnt 0
           d = nextMonth.add('days', 1).date()
+          formattedNextMonth = @_formatDate new Date(@_state.year, @_state.month + 1, d)
           if (index++) % 7 is 0
             html.push '

'
-          html.push "#{d}	"
+          html.push "#{d}	"

       html.push '
'

@@ -248,6 +252,11 @@
         @_state.month -= 1
       @render()

+    # Navigate to the previous month and select the date clicked
+    prevSelect: (e) =>
+      @prev
+      @_selectDate e
+
     # Navigate to next month.
     next: =>
       if @_state.month is 12
@@ -257,6 +266,11 @@
         @_state.month += 1
       @render()

+    # Navigate to the next month and select the date clicked
+    nextSelect: (e) =>
+      @next
+      @_selectDate e
+
     _cancelEvent: (e) => e.stopPropagation(); e.preventDefault()

     _onShortcutClick: (e) =>


Knowledge Base Prep: Backport bugfix for cache key writing race condition in SearchableAjaxSelect

--- app/assets/javascripts/app/lib/app_post/z_searchable_ajax_select.coffee
+++ app/assets/javascripts/app/lib/app_post/z_searchable_ajax_select.coffee
@@ -13,11 +13,11 @@
     # create cache and cache key
     @searchResultCache = @searchResultCache || {}

-    @cacheKey = "#{objectString}+#{@query}"
+    cacheKey = "#{objectString}+#{@query}"

     # use cache for search result
-    if @searchResultCache[@cacheKey]
-      return @onAjaxResponse( @searchResultCache[@cacheKey] )
+    if @searchResultCache[cacheKey]
+      return @renderResponse( @searchResultCache[cacheKey] )

     # add timeout for loader icon
     clearTimeout @loaderTimeoutId
@@ -32,17 +32,17 @@
         query: @query
         limit: @options.attribute.limit
       processData: true
-      success:     @onAjaxResponse
+      success:     (data, status, xhr) =>
+        # cache search result
+        @searchResultCache[cacheKey] = data
+
+        @renderResponse(data)
     )

-  onAjaxResponse: (data, status, xhr) =>
-
+  renderResponse: (data) =>
     # clear timout and remove loader icon
     clearTimeout @loaderTimeoutId
     @el.removeClass('is-loading')
-
-    # cache search result
-    @searchResultCache[@cacheKey] = data

     # load assets
     App.Collection.loadAssets(data.assets)


:lipstick:

:lipstick:

--- src/browser/appwindow.coffee
+++ src/browser/appwindow.coffee
@@ -19,8 +19,8 @@
     windowOpts =
       width: 800
       height: 600
-      'auto-hide-menu-bar': false #process.platform is 'win32'
-      title: "Main Window"
+      'auto-hide-menu-bar': process.platform is 'win32'
+      title: options.title ? "You Should Set options.title"
       'web-preferences':
         'subpixel-font-scaling': true
         'direct-write': true


only dereference return value if id is present and it's an object

--- shared/src/model/ui-settings.coffee
+++ shared/src/model/ui-settings.coffee
@@ -28,8 +28,8 @@
     SETTINGS.replace(settings)

   get: (key, id) ->
-    obj = SETTINGS.get(key) or {}
-    if isNil(id) then obj else obj[id]
+    obj = SETTINGS.get(key)
+    if (not isNil(id) and isObject(obj)) then obj[id] else obj

   set: (key, id, value) ->
     if isObject(key)


Fix section in a comment

Fix section in a comment

--- components/RemoteLogin.coffee
+++ components/RemoteLogin.coffee
@@ -19,7 +19,7 @@
       unless data.access_token
         return callback null, null

-      callback null, data.access_token # always expect access_token, http://tools.ietf.org/html/rfc6749#section-4.2.2
+      callback null, data.access_token # always expect access_token, http://tools.ietf.org/html/rfc6749#section-5.1
   # get token directly from provider
   if '$NOFLO_OAUTH_CLIENT_SECRET' isnt ''
     redirect = params.redirect or window.location.href


Remove code duplication regarding format

--- components/Resize-node.coffee
+++ components/Resize-node.coffee
@@ -70,60 +70,33 @@
             width = c.defaultDimension
           else
             height = c.defaultDimension
-        # Try to preserve the same format, if there's EXIF
-        if metadata.exif?
-          inputBuffer
-          .resize width, height
-          .withMetadata()
-          .withoutEnlargement()
-          .toBuffer (err, outputBuffer, info) ->
-            if err
-              return callback err
-            if width
-              originalWidth = metadata.width
-              resizedWidth = info.width
-              factor = originalWidth / resizedWidth
-            else
-              originalHeight = metadata.height
-              resizedHeight = info.height
-              factor = originalHeight / resizedHeight
-            out.out.send outputBuffer
-            out.factor.send factor
-            out.original.send
-              width: metadata.width
-              height: metadata.height
-            out.resized.send
-              width: info.width
-              height: info.height
-            out.metadata.send metadata
-            do callback
-        else
-          inputBuffer
-          .resize width, height
-          .withMetadata()
-          .withoutEnlargement()
-          .toFormat 'png'
-          .toBuffer (err, outputBuffer, info) ->
-            if err
-              return callback err
-            if width
-              originalWidth = metadata.width
-              resizedWidth = info.width
-              factor = originalWidth / resizedWidth
-            else
-              originalHeight = metadata.height
-              resizedHeight = info.height
-              factor = originalHeight / resizedHeight
-            out.out.send outputBuffer
-            out.factor.send factor
-            out.original.send
-              width: metadata.width
-              height: metadata.height
-            out.resized.send
-              width: info.width
-              height: info.height
-            out.metadata.send metadata
-            do callback
+        format = if metadata.exif? then 'jpeg' else 'png'
+        inputBuffer
+        .resize width, height
+        .withMetadata()
+        .withoutEnlargement()
+        .toFormat format
+        .toBuffer (err, outputBuffer, info) ->
+          if err
+            return callback err
+          if width
+            originalWidth = metadata.width
+            resizedWidth = info.width
+            factor = originalWidth / resizedWidth
+          else
+            originalHeight = metadata.height
+            resizedHeight = info.height
+            factor = originalHeight / resizedHeight
+          out.out.send outputBuffer
+          out.factor.send factor
+          out.original.send
+            width: metadata.width
+            height: metadata.height
+          out.resized.send
+            width: info.width
+            height: info.height
+          out.metadata.send metadata
+          do callback
     catch err
       return callback err


No return value.

No return value.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -651,6 +651,7 @@
     searchError: ->
       $(document).on 'ajax:error', '[data-behavior~=search_form]', (event, xhr) ->
         feedbin.showNotification('Search error.');
+        return

 jQuery ->
   $.each feedbin.init, (i, item) ->


Use '' instead of

Use '' instead of

--- app/assets/javascripts/games.js.coffee
+++ app/assets/javascripts/games.js.coffee
@@ -142,7 +142,7 @@
   $.ajax
     url: document.URL + '/hand'
     success: (playerHand) ->
-      console.info("Generating player hand cards...")
+      console.info('Generating player hand cards...')
       currentNumCard = $('#player-hand')[0].children.length
       cards = playerHand.white_cards
       Helpers.generateCards(cards.slice(currentNumCard, cards.length), 'white', 'player', false)
@@ -152,7 +152,7 @@
             cardId = $(this).attr('card-id')
             $.ajax
               type: 'POST'
-              url: document.URL + "/submissions?card_id=" + cardId
+              url: document.URL + '/submissions?card_id=' + cardId
               success: ->
                 console.info('Card was successfully submitted')
                 $('.white-card[card-id=' + cardId + ']').remove();


prototyping the next x axis

prototyping the next x axis

--- app/assets/javascripts/power.js.coffee
+++ app/assets/javascripts/power.js.coffee
@@ -65,11 +65,16 @@
     yAxis.render()

     # time = new Rickshaw.Fixtures.Time
-    # days = time.unit 'days'
-    # xAxis = new Rickshaw.Graph.Axis.Time
-    #   timeUnit: days
+    # time.units.push
+    #   name: 'day_of_month'
+    #   seconds: 86400
+    #   formatter: (d)-> "November #{d.getUTCDate()}"
+
+    # tUnit = time.unit 'day_of_month'
+    # window.xAxis = new Rickshaw.Graph.Axis.Time
     #   graph: graph
-
+    #   timeUnit: tUnit
+
     # xAxis.render();

     try


A little improvement on milestone graph representation

--- app/coffee/directives/taskboard.coffee
+++ app/coffee/directives/taskboard.coffee
@@ -15,10 +15,12 @@
         ctx = $("#dashboard-chart").get(0).getContext("2d")

         options =
-            animation: false,
-            bezierCurve: false,
-            scaleFontFamily : "'ColabThi'",
+            animation: false
+            bezierCurve: false
+            scaleFontFamily : "'ColabThi'"
             scaleFontSize : 10
+            datasetFillXAxis: 0
+            datasetFillYAxis: 0

         data =
             labels : _.map(scope.milestoneStats.days, (day) -> day.name)


facebook invite select component

facebook invite select component

--- client/game.coffee
+++ client/game.coffee
@@ -366,4 +366,4 @@
         # viewer is only a spectator, allowing the viewer to watch or perhaps
         # "clone" the game.
         noughts.state.changeState(noughts.state.PLAY,
-            noughts.state.UrlBehavior.PRESERVE_URL)
+            noughts.state.UrlBehavior.PRESERVE_URL)


no need to support $DYALOG_IDE_THEME

no need to support $DYALOG_IDE_THEME

--- client/init.coffee
+++ client/init.coffee
@@ -52,10 +52,10 @@
     )

   # CSS class to indicate theme
-  # https://nodejs.org/api/process.html#process_process_platform
-  # https://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today
-  # The theme can be overridden through localStorage.theme or an environment variable $DYALOG_IDE_THEME
-  if !prefs.theme() then prefs.theme D.process?.env?.DYALOG_IDE_THEME || do ->
+  if !prefs.theme() then prefs.theme do ->
+    # Detect platform
+    #   https://nodejs.org/api/process.html#process_process_platform
+    #   https://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today
     p = (D.process ? navigator).platform
     if /^(darwin|mac|ipad|iphone|ipod)/i.test p then 'cupertino'
     else if /^(linux|x11|android)/i.test p then 'freedom'


#268: allow any character manually entered

--- chrome/content/zotero-better-bibtex/BetterBibTeXPatternFormatter.coffee
+++ chrome/content/zotero-better-bibtex/BetterBibTeXPatternFormatter.coffee
@@ -33,7 +33,7 @@
   concat: (pattern) ->
     result = (@reduce(part) for part in pattern)
     result = (part for part in result when part)
-    return @safechars(result.join('')).trim()
+    return result.join('').trim()

   reduce: (step) ->
     value = @methods[step.method].apply(@, step.arguments)


client: home: remove unused file

client: home: remove unused file

--- client/home/lib/utilities/homeutilitiescustomerfeedbacksecondary.coffee
+++ client/home/lib/utilities/homeutilitiescustomerfeedbacksecondary.coffee
@@ -1,28 +0,0 @@
-kd             = require 'kd'
-JView          = require 'app/jview'
-KodingSwitch   = require 'app/commonviews/kodingswitch'
-
-
-module.exports = class HomeUtilitiesCustomerFeedbackSecondary extends kd.CustomHTMLView
-
-  JView.mixin @prototype
-
-  constructor: (options = {}, data) ->
-
-    super options, data
-
-    @input = new kd.InputView
-    @button = new kd.ButtonView
-
-
-  pistachio: ->
-    """
-

-    **Required: Chatlio.com requires slack integration**
-    Enable Chatlio.com for real-time customer feedback
-    <filedset>
-    Chatlio.io API Key
-    {{> @input}}
-    {{> @button}}
-

-    """