Post Cloaking:

* We now use a new custom view, {{cloaked-collection}} to display posts in a topic.

* Posts are removed and inserted (cloaked/uncloaked) into the DOM dynamically based on whether they
  are visible in the current browser viewport.

* There's been a lot of refactoring to ensure the relationship between the post views and the topic
  controller is sane.

* Lots of fixes involving jumping to a post, including a new LockOn component to that tries to stay
  focused on an element even if stuff is loading before it in the DOM that would normally push it
  down.
This commit is contained in:
Robin Ward
2013-11-20 16:33:36 -05:00
parent 8a9bef944f
commit 40f86829f7
21 changed files with 631 additions and 557 deletions

View File

@@ -2258,7 +2258,7 @@ function suspendListener(obj, eventName, target, method, callback) {
Suspends multiple listeners during a callback.
@method suspendListeners
@for Ember
@param obj
@@ -2326,7 +2326,7 @@ function watchedEvents(obj) {
is skipped, and once listeners are removed. A listener without
a target is executed on the passed object. If an array of actions
is not passed, the actions stored on the passed object are invoked.
@method sendEvent
@for Ember
@param obj
@@ -3099,14 +3099,14 @@ Map.create = function() {
Map.prototype = {
/**
This property will change as the number of objects in the map changes.
@property length
@type number
@default 0
*/
length: 0,
/**
Retrieve the value associated with a given key.
@@ -12589,7 +12589,7 @@ Ember.computed.sort = function (itemsKey, sortDefinition) {
(function() {
/**
Expose RSVP implementation
Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md
@class RSVP
@@ -14838,7 +14838,7 @@ function installPromise(proxy, promise) {
controller.get('lastName') //=> 'Penner'
```
If the controller is backing a template, the attributes are
If the controller is backing a template, the attributes are
bindable from within that template
```handlebars
@@ -16338,7 +16338,7 @@ function classToString() {
if (this[NAME_KEY]) {
ret = this[NAME_KEY];
} else if (this._toString) {
ret = this._toString;
ret = this._toString;
} else {
var str = superClassString(this);
if (str) {
@@ -17935,8 +17935,8 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
```javascript
songsController.get('content').get('firstObject'); // Returns the unsorted original content
songsController.get('firstObject'); // Returns the sorted content.
```
```
Although the sorted content can also be accessed through the arrangedContent property,
it is preferable to use the proxied class and not the arrangedContent array directly.
@@ -18025,7 +18025,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
/**
Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction.
Also sets up observers for each sortProperty on each item in the content Array.
@property arrangedContent
*/
@@ -22075,7 +22075,7 @@ Ember.merge(inBuffer, {
// when a view is rendered in a buffer, rerendering it simply
// replaces the existing buffer with a new one
rerender: function(view) {
throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM." + view.get('content.id'));
},
// when a view is rendered in a buffer, appending a child
@@ -23581,7 +23581,7 @@ define("metamorph",
/**
* @public
*
*
* Remove this object (including starting and ending
* placeholders).
*
@@ -27990,7 +27990,7 @@ helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
var buffer = '', hashTypes, hashContexts;
data.buffer.push("<option value=\"\">");
hashTypes = {};
@@ -28001,7 +28001,7 @@ function program1(depth0,data) {
}
function program3(depth0,data) {
var stack1, hashTypes, hashContexts;
hashTypes = {};
hashContexts = {};
@@ -28010,7 +28010,7 @@ function program3(depth0,data) {
else { data.buffer.push(''); }
}
function program4(depth0,data) {
var hashContexts, hashTypes;
hashContexts = {'content': depth0,'label': depth0};
hashTypes = {'content': "ID",'label': "ID"};
@@ -28021,7 +28021,7 @@ function program4(depth0,data) {
}
function program6(depth0,data) {
var stack1, hashTypes, hashContexts;
hashTypes = {};
hashContexts = {};
@@ -28030,7 +28030,7 @@ function program6(depth0,data) {
else { data.buffer.push(''); }
}
function program7(depth0,data) {
var hashContexts, hashTypes;
hashContexts = {'content': depth0};
hashTypes = {'content': "ID"};
@@ -28048,7 +28048,7 @@ function program7(depth0,data) {
stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
return buffer;
}),
attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'],
@@ -30515,7 +30515,7 @@ var get = Ember.get;
*/
/**
Finds a controller instance.
@for Ember
@@ -30531,7 +30531,7 @@ Ember.controllerFor = function(container, controllerName, lookupOptions) {
The type of generated controller depends on the context.
You can customize your generated controllers by defining
`App.ObjectController` and `App.ArrayController`
@for Ember
@method generateController
@private
@@ -31237,7 +31237,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, {
Transition into another route. Optionally supply model(s) for the
route in question. If multiple models are supplied they will be applied
last to first recursively up the resource tree (see Multiple Models Example
below). The model(s) will be serialized into the URL using the appropriate
below). The model(s) will be serialized into the URL using the appropriate
route's `serialize` hook. See also 'replaceWith'.
Simple Transition Example
@@ -31294,7 +31294,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, {
/**
Transition into another route while replacing the current URL, if possible.
This will replace the current history entry instead of adding a new one.
This will replace the current history entry instead of adding a new one.
Beside that, it is identical to `transitionTo` in all other respects. See
'transitionTo' for additional information regarding multiple models.
@@ -32650,44 +32650,44 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
To override this option for your entire application, see
"Overriding Application-wide Defaults".
### Disabling the `link-to` helper
By default `{{link-to}}` is enabled.
By default `{{link-to}}` is enabled.
any passed value to `disabled` helper property will disable the `link-to` helper.
static use: the `disabled` option:
```handlebars
{{#link-to 'photoGallery' disabled=true}}
Great Hamster Photos
{{/link-to}}
```
dynamic use: the `disabledWhen` option:
```handlebars
{{#link-to 'photoGallery' disabledWhen=controller.someProperty}}
Great Hamster Photos
{{/link-to}}
```
any passed value to `disabled` will disable it except `undefined`.
to ensure that only `true` disable the `link-to` helper you can
override the global behaviour of `Ember.LinkView`.
```javascript
```javascript
Ember.LinkView.reopen({
disabled: Ember.computed(function(key, value) {
if (value !== undefined) {
this.set('_isDisabled', value === true);
if (value !== undefined) {
this.set('_isDisabled', value === true);
}
return value === true ? get(this, 'disabledClass') : false;
})
});
```
see "Overriding Application-wide Defaults" for more.
### Handling `href`
`{{link-to}}` will use your application's Router to
fill the element's `href` property with a url that
@@ -33007,13 +33007,13 @@ var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) {
/**
Calling ``{{render}}`` from within a template will insert another
Calling ``{{render}}`` from within a template will insert another
template that matches the provided name. The inserted template will
access its properties on its own controller (rather than the controller
of the parent template).
If a view class with the same name exists, the view class also will be used.
Note: A given controller may only be used *once* in your app in this manner.
A singleton instance of the controller will be created for you.
@@ -33035,7 +33035,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
<h1>My great app</h1>
{{render navigaton}}
```
```html
<h1>My great app</h1>
<div class='ember-view'>
@@ -33259,8 +33259,8 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
App.ApplicationController = Ember.Controller.extend({
actions: {
anActionName: function() {
}
}
}
});
```
@@ -33580,7 +33580,7 @@ Ember.ControllerMixin.reopen({
this.resource('blogPost', {path:':blogPostId'}, function(){
this.resource('blogComment', {path: ':blogCommentId'});
});
aController.transitionToRoute('blogComment', aPost, aComment);
```
@@ -33611,7 +33611,7 @@ Ember.ControllerMixin.reopen({
/**
Transition into another route while replacing the current URL, if possible.
This will replace the current history entry instead of adding a new one.
This will replace the current history entry instead of adding a new one.
Beside that, it is identical to `transitionToRoute` in all other respects.
```javascript
@@ -33635,7 +33635,7 @@ Ember.ControllerMixin.reopen({
this.resource('blogPost', {path:':blogPostId'}, function(){
this.resource('blogComment', {path: ':blogCommentId'});
});
aController.replaceRoute('blogComment', aPost, aComment);
```
@@ -33825,7 +33825,7 @@ Ember.View.reopen({
// Add a new named queue after the 'actions' queue (where RSVP promises
// resolve), which is used in router transitions to prevent unnecessary
// loading state entry if all context promises resolve on the
// loading state entry if all context promises resolve on the
// 'actions' queue first.
var queues = Ember.run.queues,
@@ -35165,7 +35165,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin
Call `advanceReadiness` after any asynchronous setup logic has completed.
Each call to `deferReadiness` must be matched by a call to `advanceReadiness`
or the application will never become ready and routing will not begin.
@method advanceReadiness
@see {Ember.Application#deferReadiness}
*/
@@ -36220,7 +36220,7 @@ var slice = [].slice,
* Register/Unregister additional test helpers.
* Setup callbacks to be fired when the test helpers are injected into
your application.
@class Test
@namespace Ember
*/
@@ -36967,7 +36967,7 @@ Ember.StateManager = generateRemovedClass("Ember.StateManager");
/**
This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states
@class StateManager
@namespace Ember
*/
@@ -36976,7 +36976,7 @@ Ember.State = generateRemovedClass("Ember.State");
/**
This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states
@class State
@namespace Ember
*/

54
vendor/assets/javascripts/lock-on.js vendored Normal file
View File

@@ -0,0 +1,54 @@
(function (exports) {
var scrollEvents = "scroll.lock-on touchmove.lock-on mousedown.lock-on wheel.lock-on DOMMouseScroll.lock-on mousewheel.lock-on keyup.lock-on";
var LockOn = function(selector, options) {
this.selector = selector;
this.options = options || {};
};
LockOn.prototype.elementTop = function() {
var offsetCalculator = this.options.offsetCalculator;
return $(this.selector).offset().top - (offsetCalculator ? offsetCalculator() : 0);
};
LockOn.prototype.lock = function() {
var self = this,
previousTop = this.elementTop(),
startedAt = new Date().getTime()
i = 0;
$(window).scrollTop(previousTop);
var interval = setInterval(function() {
i = i + 1;
var top = self.elementTop(),
scrollTop = $(window).scrollTop();
if ((top !== previousTop) || (scrollTop !== top)) {
$(window).scrollTop(top);
previousTop = top;
}
// We commit suicide after 1s just to clean up
var nowTime = new Date().getTime();
if (nowTime - startedAt > 1000) {
$('body,html').off(scrollEvents)
clearInterval(interval);
}
}, 50);
$('body,html').off(scrollEvents).on(scrollEvents, function(e){
if ( e.which > 0 || e.type === "mousedown" || e.type === "mousewheel" || e.type === "touchmove") {
$('body,html').off(scrollEvents);
clearInterval(interval);
}
})
};
exports.LockOn = LockOn;
})(window);